Комментарии 16
вызов метода/свойства всегда занимает больше тактов процессора, чем просто обращение к локальной переменной
Если нет inline и оптимизаций, заточенных на определенный код.
если методы/свойства виртуальные
Сейчас JIT'ы умеют делать девиртуализацию.
Более того, есть даже вот такое поведение (в статье про Java).
Поэтому есть главное правило: нельзя теоритезировать в вопросах производительности. Единственный ответ — это цифры бенчмарка. Всё остальное — красивые слова, не более.
1. При использовании multi-dimensional array JIT не может сам сделать подобную оптимизацию и вынести длину в внешнее число, потому как не знает, что результат GetLength не меняется. Если вдруг вы задумались о производительности, то используйте в этой ситуации jagged array (массив массивов), где гарантирована статическая длина каждого массива. Почитать об этом можно в огромном количестве мест.
2. При всех своих плюсах jagged array состоит из разрозненных блоков памяти, а не хранится в одном участке. Для высокой скорости надо бы использовать одномерный массив размера [a*b]. Естественно, для адресации придется использовать умножение или увеличивать внутренний счетчик. Разница в скорости между multi-dimensional, jagged и single-dimensional настолько отличается, что мизерные отличия с выносом длины в внешнюю переменную стираются.
3. Не экономьте на спичках! Забудьте то, что я сказал, забудьте эту и предыдущую статью. Пишите рабочий, красивый и документированный код, а не оптимизированный, нечитабельный и с глюками!
[RyuJitX64Job]
public class ForLoopBench
{
private const int N = 1;
private const int X = Int16.MaxValue;
private const int Y = Byte.MaxValue;
private Random rnd = new Random(DateTime.UtcNow.Millisecond);
[Benchmark]
public void TwoDimArray_ForLoop_NestedLoop()
{
int[,] arr = new int[X, Y];
for (int i = 0; i < arr.GetLength(0); i++)
{
for (int j = 0; j < arr.GetLength(1); j++)
{
arr[i, j] = i + j;
}
}
}
[Benchmark]
public void TwoDimArray_ForLoop_NestedLoop_LenVar()
{
int[,] arr = new int[X, Y];
int len1 = arr.GetLength(0), len2 = arr.GetLength(1);
for (int i = 0; i < len1; i++)
{
for (int j = 0; j < len2; j++)
{
arr[i, j] = i + j;
}
}
}
[Benchmark]
public void TwoDimArray_ForLoop_Flat()
{
int[,] arr = new int[X, Y];
for (int i = 0; i < arr.GetLength(0) * arr.GetLength(1); i++)
{
arr[i % arr.GetLength(0), i % arr.GetLength(1)] = i;
}
}
[Benchmark]
public void TwoDimArray_ForLoop_Flat_LenVar()
{
int[,] arr = new int[X, Y];
var len1 = arr.GetLength(0);
var len2 = arr.GetLength(1);
var len = len1 * len2;
for (int i = 0; i < len; i++)
{
arr[i % len1, i % len2] = i;
}
}
[Benchmark]
public int[] OneDimArray_ForLoop_Flat()
{
int[] arr = new int[X * Y];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = i;
}
return arr;
}
[Benchmark]
public int[] OneDimArray_ForLoop_NestedLoop()
{
int[] arr = new int[X * Y];
for (int i = 0; i < arr.Length / X; i++)
{
for (int j = 0; j < arr.Length / Y; j++)
{
arr[i + j * Y] = i + j * Y;
}
}
return arr;
}
[Benchmark]
public int[] OneDimArray_ForLoop_NestedLoop_Rand()
{
int[] arr = new int[X * Y];
for (int i = 0; i < arr.Length / X; i++)
{
for (int j = 0; j < arr.Length / Y; j++)
{
arr[i + j * Y] = rnd.Next(Int32.MinValue, Int32.MaxValue);
}
}
return arr;
}
}
Резултьтаты:
| Method | Mean | Error | StdDev |
|-------------------------------------- |-----------|-----------|-----------|
| TwoDimArray_ForLoop_NestedLoop | 165.63 ms | 9.2170 ms | 27.176 ms |
| TwoDimArray_ForLoop_NestedLoop_LenVar | 44.66 ms | 0.8797 ms | 1.233 ms |
| TwoDimArray_ForLoop_Flat | 512.00 ms | 9.9678 ms | 11.866 ms |
| TwoDimArray_ForLoop_Flat_LenVar | 164.14 ms | 3.2359 ms | 5.917 ms |
| OneDimArray_ForLoop_Flat | 29.80 ms | 1.4352 ms | 4.232 ms |
| OneDimArray_ForLoop_NestedLoop | 91.30 ms | 1.7957 ms | 2.688 ms |
| OneDimArray_ForLoop_NestedLoop_Rand | 335.05 ms | 6.7007 ms | 16.811 ms |
BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.471 (1803/April2018Update/Redstone4)
Intel Core i5-2430M CPU 2.40GHz (Sandy Bridge), 1 CPU, 4 logical and 2 physical cores
[Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3260.0
RyuJitX64 : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3260.0
Job=RyuJitX64 Jit=RyuJit Platform=X64
Выводы:
- Самое дорогое в коде автора — вызов rnd.Next() (80%)
- Nomad1 прав — одномерный массив выгоднее, и не нужно отдельной переменной для длины (30 ms против 45 ms).
- Вложенность циклов дешевле, чем многомерность массивов.
Т.е. я предпочитаю не задумываясь чуть проиграть чем проиграть много.
Если вы используете for то значит производительность важнее чем читабельность. По той же причине, не согласен с автором предыдущего комментария. Если вы забрались в дебри где вам понадобился for, то стоит потратить время на бенчмарк.
Почему вы вообще работаете с массивами? Для каждой задачи нужно выбрать, а еще лучше реализовать твой тип коллекции, кторый нужен для задачи. Массивы очень гибкий и опасный элемент, на равне с указателями. Для любой повседневной задачи есть более идиоматичные и безопасные решения.
Массив это безопасная коллекция фиксированной длины, полностью реализующая IEnumerable и частично IList/ICollection (Insert, Remove, Add не доступны). Ее использование диктуется ее свойствами и массивы незаменимы при работе с изображениями, матрицами/тензорами, буферами и вообще любыми данными фиксированной длины и с рандомным доступом.
Небезопасность начинается при маршалинге, unsafe трюках (указатель на первый элемент), использовании Array.Copy и прочих методов. Но никто не заставляет вас этим пользоваться для повседневных задач. Конечно же, с массивами можно легко написать кривой код, если везде использовать их вместо списков и других коллекций, это плохой стиль и premature optimization. Но и для сравнения, System.String тоже позволяет и прямую адресацию, и указатели на отдельные символы, и реализует IEnumerable, и вообще это immutable тип и при каждой операции создает свои копии, но не стоит утверждать, что из-за этого надо его выкинуть и всегда пользоваться StringBuilder.
В моей повседневной работе есть массивы, unsafe, Array.Copy и прочие приятности. Но мне кажется это, все же, исключение. В основном люди работают с покупками, пользователями, постами и комментариями, чем с массивами структур или байтов.
Массивы проблематичны тем, что у них фиксированный размер и они изменяемы. В них нельзя настроить доступ к элементами. Скажем залочить запись, если пропал инет.
Если вы работаете с высокоуровневым кодом, то массив вам скорей всего не нужен. У вас будут коллекции, списки, queues (не уверен как это правильно перевести) и тд.
for (int i = 0, n = array.Length; i < n; ++i)
for (int i = 0; i < array.Length; ++i)
А так уже и не такая большая разница.
Иначе, при вложенных циклах, но по одномерным массивам в вызове относительно медленного .GetLength(0/1) нет никакого смысла, вполне достаточно arr1.Length и arr2.Length
Когда стоит сохранять длину массива в локальную переменную в C#