这与此问题有关,但不一样:x86-64汇编的性能优化 - 对齐和分支预测与我之前的问题略有关系:无符号64位到双倍转换:为什么这个算法来自g ++
以下是一个不真实的测试用例。这种素性测试算法是不明智的。我怀疑任何真实世界的算法都不会执行如此多的小内循环(num大概是2 ** 50的大小)。在C ++ 11中:
using nt = unsigned long long;
bool is_prime_float(nt num)
{
for (nt n=2; n<=sqrt(num); ++n) {
if ( (num%n)==0 ) { return false; }
}
return true;
}
然后g++ -std=c++11 -O3 -S生成以下内容,包含RCX n和包含XMM6 sqrt(num)。请参阅我之前发布的剩余代码(在此示例中从未执行过,因为RCX永远不会变得足够大,不能被视为带符号的否定)。
jmp .L20
.p2align 4,,10
.L37:
pxor %xmm0, %xmm0
cvtsi2sdq %rcx, %xmm0
ucomisd %xmm0, %xmm6
jb .L36 // Exit the loop
.L20:
xorl %edx, %edx
movq %rbx, %rax
divq %rcx
testq %rdx, %rdx
je .L30 // Failed divisibility test
addq $1, %rcx
jns .L37
// Further code to deal with case when ucomisd can't be used
我用这个时间std::chrono::steady_clock。我一直在进行奇怪的性能变化:从添加或删除其他代码。我最终将其追踪到一个对齐问题。该命令.p2align 4,,10试图对齐2 ** 4 = 16字节边界,但只使用最多10个字节的填充来实现,我想在对齐和代码大小之间取得平衡。
我写了一个Python脚本,用.p2align 4,,10手动控制的nop指令数替换。下面的散点图显示了20次运行中最快的15次,以秒为单位的时间,在x轴上填充的字节数:
散点图
从objdump没有填充,将发生在偏移0x402f5f的PXOR指令。在笔记本电脑上运行,Sandybridge i5-3210m,turboboost 禁用,我发现了
对于0字节填充,性能较慢(0.42秒)
对于1-4字节填充(偏移0x402f60到0x402f63)稍微好一点(0.41s,在图上可见)。
对于5-20个字节填充(偏移0x402f64到0x402f73)获得快速性能(0.37s)
对于21-32字节填充(偏移0x402f74到0x402f7f)缓慢性能(0.42秒)
然后循环一个32字节的样本
因此,16字节对齐不能提供最佳性能 - 它使我们处于稍微好一点(或者从散点图中稍微变化)的区域。32加4到19的对齐可以提供最佳性能。
为什么我看到这种性能差异?为什么这似乎违反了将分支目标与16字节边界对齐的规则(参见例如英特尔优化手册)
我没有看到任何分支预测问题。这可能是一个uop缓存怪癖?
通过将C ++算法更改为sqrt(num)64位整数缓存然后使循环纯粹基于整数,我删除了问题 - 对齐现在没有任何区别。
蓝山帝景