让 g++ 使用 SHLD/SHRD 指令
Making g++ use SHLD/SHRD instructions
考虑以下代码:
#include <limits>
#include <cstdint>
using T = uint32_t; // or uint64_t
T shift(T x, T y, T n)
{
return (x >> n) | (y << (std::numeric_limits<T>::digits - n));
}
根据godbolt,clang 3.8.1为-O1、-O2、-O3生成如下汇编代码:
shift(unsigned int, unsigned int, unsigned int):
movb %dl, %cl
shrdl %cl, %esi, %edi
movl %edi, %eax
retq
虽然 gcc 6.2(即使使用 -mtune=haswell
)生成:
shift(unsigned int, unsigned int, unsigned int):
movl , %ecx
subl %edx, %ecx
sall %cl, %esi
movl %edx, %ecx
shrl %cl, %edi
movl %esi, %eax
orl %edi, %eax
ret
自 SHRD is very fast on Intel Sandybridge and later 以来,这似乎没有那么优化。无论如何重写函数以促进编译器(特别是 gcc)的优化并支持使用 SHLD/SHRD 汇编指令?
或者是否有任何 gcc -mtune
或其他选项可以鼓励 gcc 针对现代 Intel CPU 进行更好的调整?
使用 -march=haswell
,它发出 BMI2 shlx / shrx,但仍然不是 shrd。
不,我看不出有什么办法可以让 gcc 使用 SHRD
指令。
您可以通过更改 -mtune
and -march
选项来操纵 gcc 生成的输出。
Or are there any gcc -mtune or other options that would encourage gcc to tune better for modern Intel CPUs?
是的,你可以让 gcc 生成 BMI2 code:
例如:X86-64 GCC6.2 -O3 -march=znver1 //AMD Zen
生成:(Haswell 计时)。
code critical path latency reciprocal throughput
---------------------------------------------------------------
mov eax, 32 * 0.25
sub eax, edx 1 0.25
shlx eax, esi, eax 1 0.5
shrx esi, edi, edx * 0.5
or eax, esi 1 0.25
ret
TOTAL: 3 1.75
与 clang 3.8.1 相比:
mov cl, dl 1 0.25
shrd edi, esi, cl 4 2
mov eax, edi * 0.25
ret
TOTAL 5 2.25
考虑到此处的依赖链:SHRD
在 Haswell 上较慢,在 Sandybridge 上较慢,在 Skylake 上较慢。
shrx
序列的倒数吞吐量更快。
所以这取决于 post BMI 处理器 gcc 生成更好的代码,pre-BMI clang 获胜。
SHRD
在不同处理器上的时间差异很大,我可以理解为什么 gcc 不太喜欢它。
即使使用 -Os
(优化大小)gcc 仍然不会 select SHRD
。
*) 不是时序的一部分,因为要么不在关键路径上,要么变为零延迟寄存器重命名。
考虑以下代码:
#include <limits>
#include <cstdint>
using T = uint32_t; // or uint64_t
T shift(T x, T y, T n)
{
return (x >> n) | (y << (std::numeric_limits<T>::digits - n));
}
根据godbolt,clang 3.8.1为-O1、-O2、-O3生成如下汇编代码:
shift(unsigned int, unsigned int, unsigned int):
movb %dl, %cl
shrdl %cl, %esi, %edi
movl %edi, %eax
retq
虽然 gcc 6.2(即使使用 -mtune=haswell
)生成:
shift(unsigned int, unsigned int, unsigned int):
movl , %ecx
subl %edx, %ecx
sall %cl, %esi
movl %edx, %ecx
shrl %cl, %edi
movl %esi, %eax
orl %edi, %eax
ret
自 SHRD is very fast on Intel Sandybridge and later 以来,这似乎没有那么优化。无论如何重写函数以促进编译器(特别是 gcc)的优化并支持使用 SHLD/SHRD 汇编指令?
或者是否有任何 gcc -mtune
或其他选项可以鼓励 gcc 针对现代 Intel CPU 进行更好的调整?
使用 -march=haswell
,它发出 BMI2 shlx / shrx,但仍然不是 shrd。
不,我看不出有什么办法可以让 gcc 使用 SHRD
指令。
您可以通过更改 -mtune
and -march
选项来操纵 gcc 生成的输出。
Or are there any gcc -mtune or other options that would encourage gcc to tune better for modern Intel CPUs?
是的,你可以让 gcc 生成 BMI2 code:
例如:X86-64 GCC6.2 -O3 -march=znver1 //AMD Zen
生成:(Haswell 计时)。
code critical path latency reciprocal throughput
---------------------------------------------------------------
mov eax, 32 * 0.25
sub eax, edx 1 0.25
shlx eax, esi, eax 1 0.5
shrx esi, edi, edx * 0.5
or eax, esi 1 0.25
ret
TOTAL: 3 1.75
与 clang 3.8.1 相比:
mov cl, dl 1 0.25
shrd edi, esi, cl 4 2
mov eax, edi * 0.25
ret
TOTAL 5 2.25
考虑到此处的依赖链:SHRD
在 Haswell 上较慢,在 Sandybridge 上较慢,在 Skylake 上较慢。
shrx
序列的倒数吞吐量更快。
所以这取决于 post BMI 处理器 gcc 生成更好的代码,pre-BMI clang 获胜。
SHRD
在不同处理器上的时间差异很大,我可以理解为什么 gcc 不太喜欢它。
即使使用 -Os
(优化大小)gcc 仍然不会 select SHRD
。
*) 不是时序的一部分,因为要么不在关键路径上,要么变为零延迟寄存器重命名。