У меня есть следующий тестовый код, использующий измерение скорости выполнения:
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. В чем причина?
Или еще лучше используйте что-то вроде github.com/dotnet/BenchmarkDotNet
Я согласен с вами, ребята, секундомер здесь предназначен только для сокращения демо-кода. В любом случае я запускал его вручную много раз подряд, и результаты метода 2 всегда были быстрее.
BenchmarkDotNet — это стандарт де-факто для сравнительного тестирования в .NET, до такой степени, что люди просто игнорируют любые различия, которые не были получены в результате теста BDN. Возможно, вы наблюдаете за эффектами JIT-компиляции, отложенной оптимизации, которая применяется только после того, как среда выполнения обнаруживает, что путь выполняется часто, случайными задержками на вашем компьютере. BDN повторяет тесты достаточно долго, чтобы гарантировать статистически стабильный результат, и обеспечивает разминку и охлаждение между измерениями.
Исправлен код с усреднением нескольких результатов теста... В любом случае, как и ожидалось, общий результат выдает тот же.





В foreach есть оптимизации, которые применяются, если известно, что коллекция представляет собой массив определенного типа или общий список, т. е. T[] или List<T>. Если эти оптимизации применяются, он не будет генерировать перечислитель, а вместо этого будет генерировать код, очень похожий на обычный цикл for.
Ваш метод 1 использует неуниверсальный Array, поэтому может попасть в неоптимизированный путь, генерируя перечислитель, и, вероятно, потребует упаковки каждого значения из-за неуниверсального IEnumerable интерфейса.
Ваш метод 2 преобразует массивы, поэтому он должен попасть в оптимизированный путь.
Вы можете использовать https://sharplab.io, если хотите сравнить сгенерированную сборку между методами, вы увидите, что ваш Метод 1 генерирует как IL, так и asm, что намного дольше и сложнее.
В заключение, старайтесь избегать Array и отдавайте предпочтение T[] или List<T>, если вы хотите получить от своих коллекций производительность.
К сожалению, я не могу избежать этого, потому что этот неровный универсальный массив получен из библиотечного метода. Мне просто нужно использовать его и взаимодействовать с ним. Но ваше объяснение имеет для меня смысл. Спасибо за это!
Никогда не полагайтесь на тайминги в самом начале программы. Оберните оба теста в цикл из 2–3 циклов, а затем прочитайте окончательные результаты.