gpt4 book ai didi

performance - 为什么引入无用的 MOV 指令会加速 x86_64 汇编中的紧密循环?

转载 作者:行者123 更新时间:2023-12-03 04:08:46 24 4
gpt4 key购买 nike

背景:

同时优化一些Pascal使用嵌入式汇编语言编写代码时,我注意到一个不必要的 MOV 指令,并将其删除。

令我惊讶的是,删除不必要的指令导致我的程序变慢

我发现添加任意无用的MOV指令可以进一步提高性能

效果不稳定,并且根据执行顺序而变化:相同的垃圾指令向上或向下调换单行产生速度减慢

我知道CPU会进行各种优化和精简,但是,这看起来更像是黑魔法。

数据:

我的代码版本在运行 2**20==1048576 次的循环中间有条件地编译三个垃圾操作。 (周围的程序只计算 SHA-256 哈希值)。

我相当旧的机器(Intel(R) Core(TM)2 CPU 6400 @ 2.13 GHz)上的结果:

avg time (ms) with -dJUNKOPS: 1822.84 ms
avg time (ms) without: 1836.44 ms

程序循环运行 25 次,每次运行顺序随机变化。

摘录:

{$asmmode intel}
procedure example_junkop_in_sha256;
var s1, t2 : uint32;
begin
// Here are parts of the SHA-256 algorithm, in Pascal:
// s0 {r10d} := ror(a, 2) xor ror(a, 13) xor ror(a, 22)
// s1 {r11d} := ror(e, 6) xor ror(e, 11) xor ror(e, 25)
// Here is how I translated them (side by side to show symmetry):
asm
MOV r8d, a ; MOV r9d, e
ROR r8d, 2 ; ROR r9d, 6
MOV r10d, r8d ; MOV r11d, r9d
ROR r8d, 11 {13 total} ; ROR r9d, 5 {11 total}
XOR r10d, r8d ; XOR r11d, r9d
ROR r8d, 9 {22 total} ; ROR r9d, 14 {25 total}
XOR r10d, r8d ; XOR r11d, r9d

// Here is the extraneous operation that I removed, causing a speedup
// s1 is the uint32 variable declared at the start of the Pascal code.
//
// I had cleaned up the code, so I no longer needed this variable, and
// could just leave the value sitting in the r11d register until I needed
// it again later.
//
// Since copying to RAM seemed like a waste, I removed the instruction,
// only to discover that the code ran slower without it.
{$IFDEF JUNKOPS}
MOV s1, r11d
{$ENDIF}

// The next part of the code just moves on to another part of SHA-256,
// maj { r12d } := (a and b) xor (a and c) xor (b and c)
mov r8d, a
mov r9d, b
mov r13d, r9d // Set aside a copy of b
and r9d, r8d

mov r12d, c
and r8d, r12d { a and c }
xor r9d, r8d

and r12d, r13d { c and b }
xor r12d, r9d

// Copying the calculated value to the same s1 variable is another speedup.
// As far as I can tell, it doesn't actually matter what register is copied,
// but moving this line up or down makes a huge difference.
{$IFDEF JUNKOPS}
MOV s1, r9d // after mov r12d, c
{$ENDIF}

// And here is where the two calculated values above are actually used:
// T2 {r12d} := S0 {r10d} + Maj {r12d};
ADD r12d, r10d
MOV T2, r12d

end
end;

自己尝试一下:

代码已上线at GitHub如果你想自己尝试一下。

我的问题:

  • 为什么要将寄存器的内容无用地复制到 RAM有没有提高性能?
  • 为什么同样无用的指令会在某些线路上提供加速,而在其他线路上提供减速?
  • 这种行为是否可以被编译器利用?

最佳答案

速度提高的最可能原因是:

  • 插入 MOV 会将后续指令转移到不同的内存地址
  • 其中一个移动的指令是一个重要的条件分支
  • 由于分支预测表中的混叠,该分支的预测不正确
  • 移动分支消除了别名并允许正确预测分支

您的 Core2 不会为每个条件跳转保留单独的历史记录。相反,它保留所有条件跳转的共享历史记录。 global branch prediction的一个缺点如果不同的条件跳转不相关,历史就会被不相关的信息稀释。

这个小branch prediction tutorial显示分支预测缓冲区如何工作。高速缓冲存储器由分支指令地址的低部分索引。除非两个重要的不相关分支共享相同的低位,否则这种方法效果很好。在这种情况下,您最终会出现别名,从而导致许多错误预测的分支(这会导致指令管道停顿并减慢程序速度)。

如果您想了解分支错误预测如何影响性能,请查看这个出色的答案:https://stackoverflow.com/a/11227902/1001643

编译器通常没有足够的信息来知道哪些分支将使用别名以及这些别名是否重要。但是,可以使用 Cachegrind 等工具在运行时确定该信息。和 VTune .

关于performance - 为什么引入无用的 MOV 指令会加速 x86_64 汇编中的紧密循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17896714/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com