为了账号安全,请及时绑定邮箱和手机立即绑定

涉及Intel SnB系列CPU上的微编码指令的循环分支对齐

涉及Intel SnB系列CPU上的微编码指令的循环分支对齐

凤凰求蛊 2019-09-03 16:51:37
这与此问题有关,但不一样: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个字节的无用代码......)并且内存访问仍然死慢。


查看完整回答
反对 回复 2019-09-03
  • 3 回答
  • 0 关注
  • 784 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信