Длину массива в С# стоит сохранять в отдельную переменную в случае когда у нас несколько вложенных циклов, ниже пример.
Вот простой тестовый код без сохранения длины массива в переменную:
Random rnd1 = new Random(DateTime.UtcNow.Millisecond);
int[,] arr1 = new int[Int16.MaxValue, Byte.MaxValue];
for (int i = 0; i < arr1.GetLength(0); i++)
{
for (int j = 0; j < arr1.GetLength(1); j++)
{
arr1[i, j] = rnd1.Next(Int32.MinValue, Int32.MaxValue);
}
}
Вот тот же код c сохранением длины массива в переменную:
Random rnd1 = new Random(DateTime.UtcNow.Millisecond);
int[,] arr1 = new int[Int16.MaxValue, Byte.MaxValue];
int len1 = arr1.GetLength(0), len2 = arr1.GetLength(1);
for (int i = 0; i < len1; i++)
{
for (int j = 0; j < len2; j++)
{
arr1[i, j] = rnd1.Next(Int32.MinValue, Int32.MaxValue);
}
}
Код с сохранением длины массива в переменную (второй вариант) выполняется примерно на 15% быстрее.
Подобный ответ можно найти в более-менее толстых книжках по C# или .Net, но при этом умный человек постит это на Хабре и никто в комментариях не указал ему что длину массива в С# сохраняют в переменную обычно для вложенных циклов и там это действительно имеет смысл.
Я просто хотел оставить там комментарий, но без регистрации не смог, а после регистрации оказалось — что я и после регистрации не могу оставить там комментарий (так как прошло более 10 дней с момента публикации). Может кто-то заметит эту заметку и скопирует ее туда в виде комментария или вроде того.
Комментарии (12)
Nomad1
01.02.2019 21:54+160. Где IL код, где бенчмарки?
1. При использовании multi-dimensional array JIT не может сам сделать подобную оптимизацию и вынести длину в внешнее число, потому как не знает, что результат GetLength не меняется. Если вдруг вы задумались о производительности, то используйте в этой ситуации jagged array (массив массивов), где гарантирована статическая длина каждого массива. Почитать об этом можно в огромном количестве мест.
2. При всех своих плюсах jagged array состоит из разрозненных блоков памяти, а не хранится в одном участке. Для высокой скорости надо бы использовать одномерный массив размера [a*b]. Естественно, для адресации придется использовать умножение или увеличивать внутренний счетчик. Разница в скорости между multi-dimensional, jagged и single-dimensional настолько отличается, что мизерные отличия с выносом длины в внешнюю переменную стираются.
3. Не экономьте на спичках! Забудьте то, что я сказал, забудьте эту и предыдущую статью. Пишите рабочий, красивый и документированный код, а не оптимизированный, нечитабельный и с глюками!sergeyZ
02.02.2019 10:59+3Код бенчмарка BenchmarkDotNet[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; } }
ARad
02.02.2019 15:58+1Я не люблю задуматься над такими вещами, и просто всегда сохраняю в переменную, даже если это медленнее чем использовать массив, то разница будет очень маленькая, а вот выигрыш может быть значительным.
Т.е. я предпочитаю не задумываясь чуть проиграть чем проиграть много.qw1
03.02.2019 12:37Это +1 строка к размеру функции. Сложнее читать код, сложнее поддерживать.
aikixd
03.02.2019 13:52Если вы используете for то значит производительность важнее чем читабельность. По той же причине, не согласен с автором предыдущего комментария. Если вы забрались в дебри где вам понадобился for, то стоит потратить время на бенчмарк.
Nomad1
03.02.2019 18:32+1Коллега, наверное вы имеете в виду, что почти всегда можно использовать foreach. К сожалению, вы забываете, что записать в массив данные через foreach нельзя (кроме самых извращенных случаев). В этой ситуации for оказывается не дебрями, а самым читабельным методом для инициализации массивов, особенно многомерных.
aikixd
04.02.2019 14:24Почему вы вообще работаете с массивами? Для каждой задачи нужно выбрать, а еще лучше реализовать твой тип коллекции, кторый нужен для задачи. Массивы очень гибкий и опасный элемент, на равне с указателями. Для любой повседневной задачи есть более идиоматичные и безопасные решения.
a-tk
03.02.2019 14:17for (int i = 0, n = array.Length; i < n; ++i) for (int i = 0; i < array.Length; ++i)
А так уже и не такая большая разница.qw1
03.02.2019 21:49+1Согласен, но у автора не так.
Плюс, это разрушает идеоматичный цикл «по i от 0 до X-1», с непривычки заставляет задумываться над «что тут происходит?», потребуется обучение команды этой конструкции.a-tk
03.02.2019 22:55Если такая конструкция создаёт большую когнитивную нагрузку на команду, то я даже не знаю…
AntonLozovskiy
Вообще все логично, вызов метода/свойства всегда занимает больше тактов процессора, чем просто обращение к локальной переменной, особенно, если методы/свойства виртуальные. а в случаях, когда они много раз вызываются, вообще даже говорить нечего
imanushin
Если нет inline и оптимизаций, заточенных на определенный код.
Сейчас JIT'ы умеют делать девиртуализацию.
Более того, есть даже вот такое поведение (в статье про Java).
Поэтому есть главное правило: нельзя теоритезировать в вопросах производительности. Единственный ответ — это цифры бенчмарка. Всё остальное — красивые слова, не более.