for (int i = 0; i < 100000000; i++)
y += t.X;
这是很难分析的代码。您可以在使用 Debug + Windows + Disassembly 查看生成的机器代码时看到这一点。x64 代码如下所示:
0000005a xor r11d,r11d ; i = 0
0000005d mov eax,dword ptr [rbx+0Ch] ; read t.X
00000060 add r11d,4 ; i += 4
00000064 cmp r11d,5F5E100h ; test i < 100000000
0000006b jl 0000000000000060 ; for (;;)
这是经过高度优化的代码,请注意 += 运算符是如何完全消失的。您允许这种情况发生是因为您在基准测试中犯了一个错误,您根本没有使用 y 的计算值。抖动知道这一点,所以它简单地删除了无意义的添加。增加 4 也需要解释,这是循环展开优化的副作用。稍后你会看到它被使用。
因此,您必须对基准进行更改以使其现实,在末尾添加以下行:
sw.Stop();
Console.WriteLine("{0} msec, {1}", sw.ElapsesMilliseconds, y);
这会强制计算 y 的值。现在看起来完全不同了:
0000005d xor ebp,ebp ; y = 0
0000005f mov eax,dword ptr [rbx+0Ch]
00000062 movsxd rdx,eax ; rdx = t.X
00000065 nop word ptr [rax+rax+00000000h] ; align branch target
00000070 lea rax,[rdx+rbp] ; y += t.X
00000074 lea rcx,[rax+rdx] ; y += t.X
00000078 lea rax,[rcx+rdx] ; y += t.X
0000007c lea rbp,[rax+rdx] ; y += t.X
00000080 add r11d,4 ; i += 4
00000084 cmp r11d,5F5E100h ; test i < 100000000
0000008b jl 0000000000000070 ; for (;;)
仍然非常优化的代码。奇怪的 NOP 指令确保地址 008b 的跳转是有效的,跳转到与 16 对齐的地址优化了处理器中的指令解码器单元。LEA 指令是让地址生成单元生成加法的经典技巧,允许主 ALU 同时执行其他工作。这里没有其他工作要做,但如果循环体参与更多,则可以做。并且循环展开 4 次以避免分支指令。
Anyhoo,现在您实际上是在测量真实代码,而不是删除的代码。结果在我的机器上,重复测试 10 次(重要!):
y += t.X: 125 msec
y += t.Y: 125 msec
完全一样的时间。当然,应该是这样的。您无需为财产付费。
抖动在生成高质量的机器代码方面做得很好。如果你得到一个奇怪的结果,那么总是先检查你的测试代码。这是最有可能出错的代码。不是抖动,是经过彻底测试的。