Почему разница в скорости при итерации по зубчатому массиву C#?

У меня есть следующий тестовый код, использующий измерение скорости выполнения:

using System.Diagnostics;

namespace Jagged_array_test {
    internal class Program {
        static void Main(string[] args) {
            Console.WriteLine("Jagged array test.");

            var width = 1024;
            var height = 512;

            // create a 2D jagged array using "Array" of "Array"-s and fill in with data
            var jagged = Array.CreateInstance(typeof(Array), height);
            for (var i = 0; i < jagged.GetLength(0); i++) {
                var row = Array.CreateInstance(typeof(byte), width);
                for (var j = 0; j < row.Length; j++) row.SetValue((byte)0xFF, j);
                jagged.SetValue(row, i);
            }

            // flatten the jagged array to an 1D byte array using 2 slightly different methods
            var data = new byte[width * height];

            var N = 10; // number of tests
            var c = 0;
            var sw = Stopwatch.StartNew();

            // Method 1
            var m1 = new List<double>();
            for (var i = 0; i < N; i++) {
                c = 0;
                sw.Restart();
                foreach (Array row in jagged) foreach (byte col in row) data[c++] = col;
                sw.Stop();
                m1.Add(sw.Elapsed.TotalMilliseconds);
            }
            Console.WriteLine($"Method 1: {m1.Average()} ms");

            // Method 2
            var m2 = new List<double>();
            for (var i = 0; i < N; i++) {
                c = 0;
                sw.Restart();
                foreach (var row in (Array[])jagged) foreach (var col in (byte[])row) data[c++] = col;
                sw.Stop();
                m2.Add(sw.Elapsed.TotalMilliseconds);

            }
            Console.WriteLine($"Method 2: {m2.Average()} ms");

            Console.WriteLine($"Difference: {(m1.Average() / m2.Average()):F3}x");
        }
    }
}

Обнаружено, что итерация по зубчатому массиву, используемая в методе 2, происходит как минимум в 10 раз быстрее, чем итерация, используемая в методе 1. В чем причина?

Никогда не полагайтесь на тайминги в самом начале программы. Оберните оба теста в цикл из 2–3 циклов, а затем прочитайте окончательные результаты.

Mario Vernari 28.05.2024 09:41

Или еще лучше используйте что-то вроде github.com/dotnet/BenchmarkDotNet

Jon Skeet 28.05.2024 09:42

Я согласен с вами, ребята, секундомер здесь предназначен только для сокращения демо-кода. В любом случае я запускал его вручную много раз подряд, и результаты метода 2 всегда были быстрее.

Ladislav 28.05.2024 09:45

BenchmarkDotNet — это стандарт де-факто для сравнительного тестирования в .NET, до такой степени, что люди просто игнорируют любые различия, которые не были получены в результате теста BDN. Возможно, вы наблюдаете за эффектами JIT-компиляции, отложенной оптимизации, которая применяется только после того, как среда выполнения обнаруживает, что путь выполняется часто, случайными задержками на вашем компьютере. BDN повторяет тесты достаточно долго, чтобы гарантировать статистически стабильный результат, и обеспечивает разминку и охлаждение между измерениями.

Panagiotis Kanavos 28.05.2024 09:48

Исправлен код с усреднением нескольких результатов теста... В любом случае, как и ожидалось, общий результат выдает тот же.

Ladislav 28.05.2024 09:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В foreach есть оптимизации, которые применяются, если известно, что коллекция представляет собой массив определенного типа или общий список, т. е. T[] или List<T>. Если эти оптимизации применяются, он не будет генерировать перечислитель, а вместо этого будет генерировать код, очень похожий на обычный цикл for.

Ваш метод 1 использует неуниверсальный Array, поэтому может попасть в неоптимизированный путь, генерируя перечислитель, и, вероятно, потребует упаковки каждого значения из-за неуниверсального IEnumerable интерфейса.

Ваш метод 2 преобразует массивы, поэтому он должен попасть в оптимизированный путь.

Вы можете использовать https://sharplab.io, если хотите сравнить сгенерированную сборку между методами, вы увидите, что ваш Метод 1 генерирует как IL, так и asm, что намного дольше и сложнее.

В заключение, старайтесь избегать Array и отдавайте предпочтение T[] или List<T>, если вы хотите получить от своих коллекций производительность.

К сожалению, я не могу избежать этого, потому что этот неровный универсальный массив получен из библиотечного метода. Мне просто нужно использовать его и взаимодействовать с ним. Но ваше объяснение имеет для меня смысл. Спасибо за это!

Ladislav 28.05.2024 09:59

Другие вопросы по теме