我有一些简单的32位代码,它可以计算32位整数数组的乘积。内循环如下所示:
@@loop:
mov esi,[ebx]
mov [esp],esi
imul eax,[esp]
add ebx, 4
dec edx
jnz @@loop
我想要了解的是为什么上面的代码比这两个版本的代码快6%,这些代码不执行冗余内存往返:
@@loop:
mov esi,[ebx]
imul eax,esi
add ebx, 4
dec edx
jnz @@loop
和
@@loop:
imul eax,[ebx]
add ebx, 4
dec edx
jnz @@loop
后两段代码几乎同时执行,如前所述,它们比第一段慢了6%(165ms vs 155ms,2亿个元素)。
我尝试将跳转目标手动对齐到16字节边界,但没有区别。
我在英特尔i7 4770k,Windows 10 x64上运行。
注意:我知道可以通过各种优化来改进代码,但是我只对上面代码之间的性能差异感兴趣。
答案 0 :(得分:1)
我怀疑但无法确定您是否阻止了数据依赖的停顿:
代码如下所示:
@@loop:
mov esi,[ebx] # (1)Load the memory location to esi reg
(mov [esp],esi) # (1)optionally store the location on the stack
imul eax,[esp] # (3) Perform the multiplication
add ebx, 4 # (1) Add 4
dec edx # (1)decrement counter
jnz @@loop # (0**) loop
括号中的数字是指令的延迟......如果分支预测器正确猜测,则跳转为0(因为它大部分时间都会主要循环)。
所以:当乘法仍在进行时(3条指令),我们在2之后回到循环的顶部并尝试加载到内存中并且必须停止。或者我们可以做一个商店...我们可以在乘法的同时做,然后根本不停顿。
你问的虚拟商店怎么样?为什么这样做?请注意,您正在存储我们用于乘以内存的临界值。因此,处理器可以使用存储在存储器中的该值并破坏寄存器。
那么为什么处理器无法做到这一点呢?处理器无法产生比你要求更多的内存访问,或者它可能会干扰多处理器程序(想象你正在写入的缓存行是共享的,你必须通过写入它来在每个循环的其他CPU上使它无效......哎哟!)。
所有这些都是纯粹的推测,但它似乎与所有证据相符(您的代码和我对intel架构的知识......和x86汇编)。希望有人可以指出我是否有错误。