【问题标题】:Slow vpermpd instruction being generated; why?生成缓慢的 vpermpd 指令;为什么?
【发布时间】:2019-06-20 10:35:24
【问题描述】:

我有一个过滤器m_f,它作用于输入向量v via

Real d2v = m_f[0]*v[i];
for (size_t j = 1; j < m_f.size(); ++j)
{
   d2v += m_f[j] * (v[i + j] + v[i - j]);
}

perf 告诉我们这个循环的热点:

vaddpdvfma231pd 有意义;没有它们,我们当然无法执行此操作。但是缓慢的vpermpd 让我感到莫名其妙。它的作用是什么?

【问题讨论】:

    标签: c++ assembly signal-processing avx auto-vectorization


    【解决方案1】:

    这是v[i - j] 术语。由于随着j 的增加,内存访问通过内存向后移动,因此有必要进行随机播放以反转从内存中读取的 4 个值的顺序。

    【讨论】:

      【解决方案2】:

      vpermpd 应该只会在您的瓶颈是前端吞吐量(将 uops 输入到无序内核中)时减慢您的速度。

      vpermpd 并不是特别“慢”,除非您使用的是 AMD CPU。 (车道交叉 YMM shuffle 在 AMD 的 CPU 上速度较慢,因为它们必须解码为比 256 位指令拆分成的普通 2 128 位微指令更多。vpermpd 在 Ryzen 上是 3 微指令,或者 4内存源。)

      在 Intel 上,带有内存源的 vpermpd 对于前端始终为 2 uop(即使是非索引寻址模式也无法进行微熔断)。布

      如果您的循环仅运行少量迭代,那么 OoO exec 可能能够隐藏 FMA 延迟,并且可能实际上是该循环 + 周围代码的前端瓶颈。这是可能的,因为循环外的(低效的)水平和代码得到了多少计数。

      在这种情况下,也许展开 2 会有所帮助,但检查是否可以运行主循环的一次迭代的额外开销对于非常小的计数可能会变得昂贵。


      否则(对于大量计数)您的瓶颈可能在于使用 d2v 作为输入/输出操作数进行 FMA 的 4 到 5 个循环循环携带的依赖性。使用多个累加器展开,以及指针增量而不是索引,将是一个巨大的性能提升。比如 2 倍或 3 倍。

      试试 clang,它通常会为你做到这一点,而且它的 skylake/haswell 调音非常激进。 (例如clang -O3 -march=native -ffast-math

      带有-funroll-loops 的GCC 实际上并不使用多个累加器,IIRC。我有一段时间没看,我可能错了,但我认为它只会使用相同的累加器寄存器重复循环体,根本无助于并行运行更多的 dep 链。 Clang 实际上将使用 2 或 4 个不同的向量寄存器来保存 d2v 的部分和,并将它们添加到循环外的末尾。 (但对于 large 尺寸,8 或更多会更好。Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables?

      展开还值得使用指针增量,从而在英特尔 SnB 系列的 vaddpdvfmadd 指令中节省 1 uop。


      为什么 m_f.size(); 被保存在内存中 (cmp rax, [rsp+0x50]) 而不是寄存器? 您是否在禁用严格别名的情况下进行编译?循环不写入内存,所以这很奇怪。除非编译器认为循环将运行很少的迭代,所以不值得循环外的代码加载一个最大值?

      每次迭代都复制和否定j 看起来像是错过了优化。显然从循环外的 2 个寄存器开始更有效,并且每次循环迭代都使用 add rax,0x20 / sub rbx, 0x20 而不是 MOV+NEG。

      如果您有这样的 [mcve],看起来有几个可能被报告为编译器错误的优化遗漏。这个 asm 在我看来就像 gcc 输出。

      令人失望的是 gcc 使用了如此糟糕的水平和成语。 VHADDPD 是 3 个微指令,其中 2 个需要随机端口。也许尝试更新版本的 GCC,比如 8.2。虽然我不确定避免 VHADDPS/PD 是否是关闭GCC bug 80846 的一部分。该链接是我对使用 packed-single 分析 GCC 的 hsum 代码的错误的评论,使用 vhaddps 两次。

      看起来你在循环之后的 hsum 实际上是“热的”,所以你正在遭受 gcc 紧凑但效率低下的 hsum。

      【讨论】:

      • 如果您遗漏了-ffast-math,clang 甚至可能不会将乘法 + 加法收缩到 FMA 中。请参阅-ffp-contract=fast,或使用编译指示。但是您需要关联数学(作为快速数学的一部分启用)来自动矢量化归约。
      • 我用-ffast-math -O3 -march-native在gcc-8上编译它;不确定次要版本。没有给出任何别名的标志。
      • 对于产生性能图形的测试,m_f.size() 是 18。
      • 不幸的是,我无法再将godbolt链接缩小到这个程度。
      • @user14717:你的 Godbolt 链接在几个地方使用了long double,所以一堆 asm 使用标量 x87 来实现 80 位 long double 精度。输出中有两条vpermpd 指令,但在vaddpd+vfma*pd 的循环中没有一条指令,只有FMA 或FMA+sub。你在哪个CPU上? -march=native 如果你不说你正在编译的 CPU 是不可重现的。您是否可能在 Windows 上,long doubledouble(64 位)相同?
      猜你喜欢
      • 1970-01-01
      • 2017-02-13
      • 2012-04-06
      • 2022-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-19
      相关资源
      最近更新 更多