Ответ 1
Прежде всего, профилирование на уровне С# не даст нам ничего, так как покажет нам строку кода С#, выполнение которой длится больше всего, что, конечно, является инициализацией встроенного массива, но для спорта:
Теперь, когда мы увидим ожидаемые результаты, давайте наблюдаем код на уровне IL и пытаемся увидеть, чем отличаются инициализации двух массивов:
Прежде всего мы рассмотрим стандартную инициализацию массива:
Все выглядит хорошо, цикл делает именно то, что мы ожидаем, без заметных накладных расходов.
Теперь давайте посмотрим на инициализацию встроенного массива:
- Первые 2 строки создают массив размером 4.
- Третья строка дублирует созданный указатель массива на стек оценки.
- В последней строке для массива указывается только что созданный массив.
Теперь сосредоточимся на двух оставшихся строках:
Первая строка (L_001B
) загружает некоторый Compilation-Time-Type с именем типа __StaticArrayInitTypeSize=16
и именем поля 1456763F890A84558F99AFA687C36B9037697848
, который находится внутри класса с именем <PrivateImplementationDetails>
в Root Namespace
. если мы посмотрим на это поле, то увидим, что оно содержит искомый массив полностью так, как мы хотим, чтобы он был закодирован в байтах:
.field assembly static initonly valuetype <PrivateImplementationDetails>/__StaticArrayInitTypeSize=16 1456763F890A84558F99AFA687C36B9037697848 = ((01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00))
Во второй строке вызывается метод, который возвращает инициализированный массив, используя пустой массив, который мы только что создали в L_0060
, и используя этот тип времени компиляции.
Если мы попытаемся взглянуть на этот код метода, то увидим, что он реализован в CLR:
[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical, __DynamicallyInvokable]
public static extern void InitializeArray(Array array, RuntimeFieldHandle fldHandle);
Так что либо нам нужно найти его исходный код в опубликованных источниках CLR, который я не смог найти для этого метода, либо мы можем отладить на уровне сборки. Так как у меня сейчас проблемы с моей Visual-Studio и возникают проблемы с ее сборкой, давайте попробуем по-другому взглянуть и посмотрим на запись памяти для каждой инициализации массива.
Начиная с инициализации цикла, в начале мы можем видеть, что инициализирован пустой TG48 (на рисунке 0x724a3c88
, показанном в Little-Endian, тип int[]
, а 0x00000004
- размер массива, чем мы можем видеть 16 байтов нулей).
Когда массив инициализирован, мы видим, что память заполнена индикаторами того же типа и размера, только в ней есть цифры от 0 до 3:
Когда цикл повторяется, мы видим, что следующий массив (отмечен красным) выделен сразу после нашего первого массива (не подписан), что также подразумевает, что каждый массив потребляет 16 + type + size + padding = 19 bytes
:
Выполняя тот же процесс на inline-type-initializer, мы видим, что после инициализации массива куча содержит другие типы, также отличные от нашего массива; Вероятно, это происходит из-за метода System.Runtime.CompilerServices.InitializeArray
, поскольку указатель массива и токен типа времени компиляции загружаются в стек оценки, а не в кучу (строки L_001B
и L_0020
в коде IL):
Теперь выделение следующего массива с помощью встроенного инициализатора массива показывает нам, что следующий массив выделяется только через 64 байта после начала первого массива!
Поэтому inline-array-initializer какминимум медленнее по нескольким причинам:
- Намного больше памяти выделяется (нежелательная память из CLR).
- В дополнение к конструктору массива есть накладные расходы на вызов метода.
- Кроме того, если CLR выделил больше памяти, кроме массива - он, вероятно, выполняет еще несколько ненужных действий.
Теперь о разнице между Debug и Release в инициализаторе встроенного массива:
Если вы проверяете код сборки отладочной версии, это выглядит так:
00952E46 B9 42 5D FF 71 mov ecx,71FF5D42h //The pointer to the array.
00952E4B BA 04 00 00 00 mov edx,4 //The desired size of the array.
00952E50 E8 D7 03 F7 FF call 008C322C //Array constructor.
00952E55 89 45 90 mov dword ptr [ebp-70h],eax //The result array (here the memory is an empty array but arr cannot be viewed in the debug yet).
00952E58 B9 E4 0E D7 00 mov ecx,0D70EE4h //The token of the compilation-time-type.
00952E5D E8 43 EF FE 72 call 73941DA5 //First I thought that the System.Runtime.CompilerServices.InitializeArray method but thats the part where the junk memory is added so i guess it a part of the token loading process for the compilation-time-type.
00952E62 89 45 8C mov dword ptr [ebp-74h],eax
00952E65 8D 45 8C lea eax,[ebp-74h]
00952E68 FF 30 push dword ptr [eax]
00952E6A 8B 4D 90 mov ecx,dword ptr [ebp-70h]
00952E6D E8 81 ED FE 72 call 73941BF3 //System.Runtime.CompilerServices.InitializeArray method.
00952E72 8B 45 90 mov eax,dword ptr [ebp-70h] //Here the result array is complete
00952E75 89 45 B4 mov dword ptr [ebp-4Ch],eax
С другой стороны, код для релизной версии выглядит следующим образом:
003A2DEF B9 42 5D FF 71 mov ecx,71FF5D42h //The pointer to the array.
003A2DF4 BA 04 00 00 00 mov edx,4 //The desired size of the array.
003A2DF9 E8 2E 04 F6 FF call 0030322C //Array constructor.
003A2DFE 83 C0 08 add eax,8
003A2E01 8B F8 mov edi,eax
003A2E03 BE 5C 29 8C 00 mov esi,8C295Ch
003A2E08 F3 0F 7E 06 movq xmm0,mmword ptr [esi]
003A2E0C 66 0F D6 07 movq mmword ptr [edi],xmm0
003A2E10 F3 0F 7E 46 08 movq xmm0,mmword ptr [esi+8]
003A2E15 66 0F D6 47 08 movq mmword ptr [edi+8],xmm0
Оптимизация отладки делает невозможным просмотр памяти arr, поскольку локальный уровень IL никогда не устанавливается.
Как вы можете видеть, эта версия использует movq
, что, в этом отношении, самый быстрый способ скопировать память типа времени компиляции в инициализированный массив, дважды скопировав QWORD
(2 int
вместе!), Который является точно содержимым нашего массива, который является 16 bit
.