В этой статье я рассмотрю различные типы массивов в 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 в замкнутом цикле вряд ли имеет значение.

На практике эта финальная оптимизация, вероятно, не стоит.

Так что ты думаешь? Собираетесь ли вы провести рефакторинг своего кода массива?

Добавьте комментарий и скажите, что вы думаете!