为什么 Clang 只从 Sandy Bridge 开始使用这种优化技巧?
Why does Clang do this optimization trick only from Sandy Bridge onward?
我注意到 Clang 为以下代码片段做了一个有趣的除法优化技巧
int64_t s2(int64_t a, int64_t b)
{
return a/b;
}
如果将 march
指定为 Sandy Bridge 或更高
,则以下是程序集输出
mov rax, rdi
mov rcx, rdi
or rcx, rsi
shr rcx, 32
je .LBB1_1
cqo
idiv rsi
ret
.LBB1_1:
xor edx, edx
div esi
ret
这是 the signed version and the unsigned version
的 Godbolt 链接
据我了解,它会检查两个操作数的高位是否为零,如果是,则进行 32 位除法
我检查了 this table,发现 Core2 和 Nehalem 上 32/64 位除法的延迟分别为 40/116 和 26/89。因此,如果操作数确实通常不宽,那么通过进行 32 位除法而不是 64 位除法所节省的费用可能与 SnB
一样有价值
那么为什么它只对 SnB 和后来的微架构启用?为什么 GCC 或 ICC 等其他编译器不这样做?
我猜 clang 开发人员测试了它适用于哪些 uarches,发现它只是 SnB 系列。
听起来不错,因为 P6 系列上的一个时髦的停顿,以及 AMD 的不同 dividers。
在 P6 系列上使用移位 imm8(不是隐式移位 1)的标志结果会导致前端在发出标志读取指令之前停止,直到移位 退休。 (因为 P6 解码器不检查 imm8=0 的情况以保留标志未修改,而 SnB 会检查)。 。这可能就是 clang 不将其用于 P6 系列的原因。
可能是一种不同的方法来检查不会导致此停顿的相关条件(比如 je
之前的 test rcx,rcx
,在 Core2/Nehalem). 但如果 clang 开发者没有意识到它在 P6 系列上运行缓慢的原因,他们就不会想到修复它,而只是让它没有为 pre-SnB 目标完成。 (不幸的是,没有人将我添加到关于这个的补丁审查或错误 CC 列表中;这是我第一次看到 clang 进行这种优化。虽然我想我可能在其他一些 LLVM 审查或错误。无论如何,尝试添加一个 test
并看看这是否值得在 Nehalem 上使用可能会很有趣。)
根据 Agner Fog 的说法,AMD 的 dividers 具有相同的最佳情况 div 性能,无论操作数大小如何,大概仅取决于输入的实际大小。只有最坏的情况会随着操作数的大小而增长。 所以我 认为 它对 运行 idiv r64
无害,在 AMD 上小输入符号扩展到 128 / 64 位。(AMD 上的div/idiv 对于所有操作数大小都是 2 微指令(8 位除外,因为它只需要写入一个输出寄存器:AH 和 AL = AX。与英特尔的微编码整数 division 不同.)
英特尔 非常 不同:idiv r32
是 9 微指令,而 idiv r64
是 59 微指令,最好的吞吐量是 3 倍,在哈斯韦尔。 SnB 家族的其他成员类似。
Why don't other compilers like GCC or ICC do it?
可能是clang开发者想到的,gcc/icc还没有抄袭。如果您看过 Chandler Carruth 关于 perf
的演讲,他使用的一个例子是玩弄一根树枝来跳过 div
。我猜这种优化是他的主意。看起来很漂亮。 :)
我注意到 Clang 为以下代码片段做了一个有趣的除法优化技巧
int64_t s2(int64_t a, int64_t b)
{
return a/b;
}
如果将 march
指定为 Sandy Bridge 或更高
mov rax, rdi
mov rcx, rdi
or rcx, rsi
shr rcx, 32
je .LBB1_1
cqo
idiv rsi
ret
.LBB1_1:
xor edx, edx
div esi
ret
这是 the signed version and the unsigned version
的 Godbolt 链接据我了解,它会检查两个操作数的高位是否为零,如果是,则进行 32 位除法
我检查了 this table,发现 Core2 和 Nehalem 上 32/64 位除法的延迟分别为 40/116 和 26/89。因此,如果操作数确实通常不宽,那么通过进行 32 位除法而不是 64 位除法所节省的费用可能与 SnB
一样有价值那么为什么它只对 SnB 和后来的微架构启用?为什么 GCC 或 ICC 等其他编译器不这样做?
我猜 clang 开发人员测试了它适用于哪些 uarches,发现它只是 SnB 系列。
听起来不错,因为 P6 系列上的一个时髦的停顿,以及 AMD 的不同 dividers。
在 P6 系列上使用移位 imm8(不是隐式移位 1)的标志结果会导致前端在发出标志读取指令之前停止,直到移位 退休。 (因为 P6 解码器不检查 imm8=0 的情况以保留标志未修改,而 SnB 会检查)。
可能是一种不同的方法来检查不会导致此停顿的相关条件(比如 je
之前的 test rcx,rcx
,在 Core2/Nehalem). 但如果 clang 开发者没有意识到它在 P6 系列上运行缓慢的原因,他们就不会想到修复它,而只是让它没有为 pre-SnB 目标完成。 (不幸的是,没有人将我添加到关于这个的补丁审查或错误 CC 列表中;这是我第一次看到 clang 进行这种优化。虽然我想我可能在其他一些 LLVM 审查或错误。无论如何,尝试添加一个 test
并看看这是否值得在 Nehalem 上使用可能会很有趣。)
根据 Agner Fog 的说法,
AMD 的 dividers 具有相同的最佳情况 div 性能,无论操作数大小如何,大概仅取决于输入的实际大小。只有最坏的情况会随着操作数的大小而增长。 所以我 认为 它对 运行 idiv r64
无害,在 AMD 上小输入符号扩展到 128 / 64 位。(AMD 上的div/idiv 对于所有操作数大小都是 2 微指令(8 位除外,因为它只需要写入一个输出寄存器:AH 和 AL = AX。与英特尔的微编码整数 division 不同.)
英特尔 非常 不同:idiv r32
是 9 微指令,而 idiv r64
是 59 微指令,最好的吞吐量是 3 倍,在哈斯韦尔。 SnB 家族的其他成员类似。
Why don't other compilers like GCC or ICC do it?
可能是clang开发者想到的,gcc/icc还没有抄袭。如果您看过 Chandler Carruth 关于 perf
的演讲,他使用的一个例子是玩弄一根树枝来跳过 div
。我猜这种优化是他的主意。看起来很漂亮。 :)