这与此问题有关,但不一样: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, %xmm0cvtsi2sdq %rcx, %xmm0ucomisd %xmm0, %xmm6jb .L36 // Exit the loop.L20:xorl %edx, %edxmovq %rbx, %raxdivq %rcxtestq %rdx, %rdxje .L30 // Failed divisibility testaddq $1, %rcxjns .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位整数缓存然后使循环纯粹基于整数,我删除了问题 - 对齐现在没有任何区别。
3 回答
蓝山帝景
TA贡献1843条经验 获得超7个赞
从我在你的算法中看到的,你肯定没有太多可以改进它。
你遇到的问题可能不是分支到一个对齐的位置,尽管这仍然有帮助,你当前的问题更可能是管道机制。
当你一个接一个地写两条指令时,例如:
mov %eax, %ebx
add 1, %ebx
为了执行第二条指令,必须完成第一条指令。因此,编译器倾向于混合指令。假设你需要设置%ecx为零,你可以这样做:
mov %eax, %ebx
xor %ecx, %ecx
add 1, %ebx
在这种情况下,mov和xor都可以并行执行。这使得事情变得更快......并行处理的指令数量在处理器之间变化很大(Xeons通常更好)。
分支添加另一个参数,其中最佳处理器可以同时开始执行分支的两侧(true和false ...)。但实际上大多数处理器都会猜测并希望它们是正确的。
最后,很明显,转换sqrt()结果的整数将使事情很多更快,因为你会避免一切无感与SSE2代码,如果只用于转换+比较时,这两个指令可以用做是明确慢整数。
现在......你可能仍然想知道为什么对齐与整数无关。事实是,如果您的代码适合L1指令缓存,那么对齐并不重要。如果你丢失了L1缓存,那么它必须重新加载代码,这就是对齐变得非常重要的地方,因为在每个循环上它可能会加载无用的代码(可能是15个字节的无用代码......)并且内存访问仍然死慢。
添加回答
举报
0/150
提交
取消