В этой статье я рассмотрю различные типы массивов в C #. Понимание различий между типами массивов поможет вам выбрать правильную структуру данных для каждого случая.
Посмотрите следующий код:
Метод MeasureTestA создает трехмерный массив, просматривает каждый элемент массива и устанавливает для него значение «3».
Метод MeasureTestB делает почти то же самое, но вместо этого использует одномерный массив и вычисляет смещение массива вручную с использованием переменных цикла i, j и k.
Это не должно иметь никакого значения, правда? Можно было ожидать, что трехмерные массивы в .NET используют ту же логику, что и то, что я явно закодировал в MeasureTestB.
Что ж, посмотрим:
Вы это предвидели?
Код трехмерного массива в MeasureTestA на 31% медленнее, чем код одномерного массива в MeasureTestB!
Что тут происходит?
Давайте разберемся. Начнем с рассмотрения кода трехмерного массива в MeasureTestA. Внутренний цикл компилируется в очень эффективный промежуточный язык:
В выделенном разделе происходит волшебство. Метод Array.Set использует индексы i, j и k для записи значения «3» непосредственно в память.
Это всего 6 инструкций с одним вызовом метода. Совсем неплохо!
Теперь давайте посмотрим на внутренний цикл MeasureTestB:
Теперь во внутреннем цикле гораздо больше инструкций на промежуточном языке. И вы можете ясно видеть инструкции mul и add, которые вручную вычисляют смещение массива по индексам i, j и k.
Но заметили ли вы, что в цикле нет ни одного вызова метода?
Вместо этого в IL_0033 есть инструкция stelem, которая записывает число «3» непосредственно в одномерный массив.
И в этом причина разницы в производительности: среда выполнения .NET имеет специальные инструкции промежуточного языка для работы с одномерными массивами!
Из-за этого код в MeasureTestB вообще не нуждается в вызовах методов во внутреннем цикле, и это экономит много времени.
Поэтому, если вы работаете с многомерными массивами в горячем цикле своего кода, подумайте о сведении массивов до одного измерения. Это удалит вызов метода из тела вашего внутреннего цикла и значительно ускорит ваш код.
Наконец, давайте взглянем на MeasureTestC.
Этот метод также использует одномерный массив, но игнорирует индексы i, j и k и избегает вычисления смещения массива. Поскольку мы все равно обновляем массив последовательно, этот код просто начинается с нулевого индекса и каждый раз увеличивает индекс на единицу.
Вот как выглядит скомпилированный код:
Это настолько эффективно, насколько мы собираемся получить.
Код использует инструкцию stelem для записи числа «3» непосредственно в одномерный массив, а затем увеличивает индекс на единицу.
Обратите внимание, что нигде нет инструкций mul или add, потому что мы больше не вычисляем индекс по i, j и k.
Это дает нам небольшой прирост производительности. Код в методе C на 2% быстрее, чем в методе B.
Это просто показывает, насколько быстро выполняется целочисленное умножение на современных процессорах. Избавление от двух инструкций mul в замкнутом цикле вряд ли имеет значение.
На практике эта финальная оптимизация, вероятно, не стоит.
Так что ты думаешь? Собираетесь ли вы провести рефакторинг своего кода массива?
Добавьте комментарий и скажите, что вы думаете!