Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Упаковка ValueType при использовании IEnumerable

Допустим, имеется некий массив, например:
int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } };
Все массивы реализуют IEnumerable (не generic), таким образом, при использовании этого интерфейса все элементы будут упакованы?
Вопрос актуален, например, при использовании Linq-операций Cast() или OfType()
Console.WriteLine(string.Join(" " , array.Cast()));


Ответ

Да, ситуация с многомерными массивами довольно печальная. Такой массив реализует IEnumerable, но не реализует IEnumerable. А это означает, что любое использование многомерного массива через "призму" IEnumerable приведет к упаковке каждого элемента, и использование метода Enumerable.Cast - не исключение.
Вот простой бенчмарк (на основе BenchmarkDotNet), который показывает, что это действительно так:
[MemoryDiagnoser] public class MultidimentionalAarrayTests { private int[,] m_multiArray = {{1, 2}, {3, 4}}; private int[] m_regularArray = {1, 2, 3, 4};
[Benchmark] public int MultiArrayLast() { return m_multiArray.Cast().Last(); }
[Benchmark] public int RegularArrayLast() { return m_regularArray.Last(); } }
Результат:
Method | Mean | Error | StdDev | Gen 0 | Allocated | --------------------------- |------------:|----------:|----------:|-------:|----------:| MultiArrayLast | 1,166.97 ns | 23.229 ns | 51.473 ns | 0.0401 | 132 B | RegularArrayLast | 51.29 ns | 1.250 ns | 3.686 ns | - | 0 B |
Мы тут видим кучку аллокаций: в первом случае - упакован каждый элемент, итератор в Cast, итератор в Last. Во втором случае нет аллокаций вообще, поскольку Last проверяет, что последовательность реализует IList (а одномерный массив его реализует) и сразу же возвращает последний элемент.
Поскольку многомерные массивы не реализуют обобщенный IEnumerable, то заставить его сделать это самим мы не можем, но мы можем создать метод расширения, чтобы не использовать Enumerable.Cast
public static class MultiDimentionalArrayEx { public static IEnumerable AsEnumerable(this T[,] array) { foreach (var e in array) yield return e; } }
Теперь мы можем добавить еще один бенчмарк, чтобы проверить результат:
[Benchmark] public int MultiArrayWithAsEnumerable() { return m_multiArray.AsEnumerable().Last(); }
И вот окончательный результат:
Method | Mean | Error | StdDev | Gen 0 | Allocated | --------------------------- |------------:|-----------:|-----------:|-------:|----------:| MultiArrayLast | 1,115.45 ns | 31.0145 ns | 90.9603 ns | 0.0401 | 132 B | RegularArrayLast | 46.11 ns | 0.1826 ns | 0.1525 ns | - | 0 B | MultiArrayWithAsEnumerable | 161.74 ns | 3.2693 ns | 3.2109 ns | 0.0150 | 48 B |
Здесь мы видим, что есть выделение в куче двух итераторов (одного для метода расширения и еще одного для Enumerable.Last), но нет упаковок самих элементов.

Комментариев нет:

Отправить комментарий