首先,在 C# 级别进行分析不会给我们任何帮助,因为它会向我们展示执行时间最长的 C# 代码行,这当然是内联数组初始化,但对于运动而言:
现在,当我们看到预期的结果时,让我们在 IL 级别观察代码,并尝试看看 2 个数组的初始化有什么不同:
现在我们将关注剩下的 2 行:
第一行 (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 中创建的空数组并使用此Compile-Time-Type 返回初始化数组。
如果我们尝试查看这个方法的代码,我们会发现它是implemented within the CLR:
[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical, __DynamicallyInvokable]
public static extern void InitializeArray(Array array, RuntimeFieldHandle fldHandle);
因此,要么我们需要在已发布的 CLR 源代码中找到它的源代码,而我在此方法中找不到,要么我们可以在程序集级别进行调试。
由于我的 Visual-Studio 现在有问题,并且它的程序集视图也有问题,让我们尝试另一种态度,查看每个数组初始化的内存写入。
从循环初始化开始,一开始我们可以看到有en empty int[] initialized(图中Little-Endian中看到的0x724a3c88是int[]的类型,0x00000004是大小数组,我们可以看到 16 个字节的零)。
当数组初始化时,我们可以看到内存中充满了相同的type和size指标,只是其中还有数字0到3:
当循环迭代时,我们可以看到下一个数组(用红色标记)分配在我们的第一个数组(未签名)之后,这也意味着每个数组都消耗16 + type + size + padding = 19 bytes:
在inline-type-initializer上做同样的处理,我们可以看到在数组初始化之后,堆中还包含了我们数组之外的其他类型;这可能来自 System.Runtime.CompilerServices.InitializeArray 方法,因为数组指针和 compile-time-type 标记被加载到评估堆栈上而不是堆上(行 L_001B 和 L_0020 在IL代码):
现在使用内联数组初始化器分配下一个数组向我们展示了下一个数组仅在第一个数组开始后的 64 个字节处分配!
所以 inline-array-initializer 至少会因为几个原因而变慢:: p>
- 分配了更多内存(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's the System.Runtime.CompilerServices.InitializeArray method but thats the part where the junk memory is added so i guess it's 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 次( 2 ints 一起!)这正是我们数组的内容,即16 bit。