为啥clang++更喜欢adcx而不是adc

Posted

技术标签:

【中文标题】为啥clang++更喜欢adcx而不是adc【英文标题】:Why does clang++ prefer adcx over adc为什么clang++更喜欢adcx而不是adc 【发布时间】:2018-06-03 07:03:45 【问题描述】:

我已经使用以下代码实现了多精度加法:

    bool carry;
    std::array<uint64_t, N> r;

    for (auto i = 0; i < N; ++i) 
       uint64_t aa = a[i];
       __uint128_t res = static_cast<__uint128_t>(aa) + b[i] + carry;
       carry = res >> 64;
       r[i] = res;
    

而 clang++6.0 生成了以下程序集:

400a49: 4c 01 c1                add    %r8,%rcx
400a4c: 66 49 0f 38 f6 c1       adcx   %r9,%rax
400a52: 66 49 0f 38 f6 f2       adcx   %r10,%rsi
400a58: 66 48 0f 38 f6 d7       adcx   %rdi,%rdx

谁能解释为什么clang选择使用adcx而不是adc? 据我所知,boto 的执行时间相同,但 adc 的编码是 3 个字节,而 adcx 是 6 个字节。

更新:我玩得更久了,它的行为似乎很随机。 如果 args 作为 const 引用传递,我会得到 adcx https://godbolt.org/g/noFZNS 如果我按值传递,我会得到 adc:

https://godbolt.org/g/RkBWhV

如果代码不在函数内部,只是内联在 main 中,那就一团糟。

【问题讨论】:

carry 是什么,T 是什么,r 是什么,...minimal reproducible example 可能会有所帮助,因为您正在询问生成的特定机器代码。另外,您似乎发布了减法的 C 源代码,而不是加法?从您的组装件来看,尚不清楚为什么不使用adc。也许for 循环机制使用零标志,所以adcx 有助于保留它。 (取决于N 的定义,如果它是constexpr / define 或变量) 对我来说似乎是一个错过的优化。我认为adc 是更好的选择。 @Ped7g 你是对的我发布了错误的代码(sub 而不是 add)。 N是4。整个代码很长,所以我不知道如何发布它。 好吧,理论上adcxadox 可以是interleaved。也许这是它的副作用? 似乎 clang 3.8 和 gcc 正确生成 adc 【参考方案1】:

对我来说,这看起来像是一个错过的优化。我认为adc 是一个更好的选择。在 Skylake 上,根据一些快速吞吐量测试(xor eax,eax/times 4 adcx eax,edx 在循环中),它们具有相同的性能特征。奇怪的是,Agner Fog 没有在他的指令表 (http://agner.org/optimize/) 中列出 adox/adcx,在 SKL 上,ADC/ADCX/ADOX 对于 p0/p6 都是 1 uop,延迟为 1c。

如果有的话,写入所有标志而不仅仅是 CF 不太可能导致性能问题。

您应该在https://bugs.llvm.org/buglist.cgi 上报告此问题。

在当有两个并行的 dep 链时,clang 知道如何实际与 ADOX 交错之前,在 ADCX 上花费额外的代码大小是没有意义的。

我可以想象一个罕见的情况,保留其他标志很有用,最近的英特尔 CPU 似乎在部分标志方面非常有效,甚至不需要合并微指令。但这是非常小众的,而不是这里发生的事情(add clobbers all flags)。

【讨论】:

老实说,当adc 具有单周期延迟时,我不明白adcx/adox 的价值。如果您尝试保持两个独立的多精度加法链继续运行,您会遇到最多两个加载单元和一个存储单元的加载/存储带宽限制。 adcx/adox 是在 adc 变成单周期之前发明的,但是 AFAIK 之前它们并没有在 CPU 中实现。因此,充其量只是英特尔内部的通信故障。 @EOF:预期的用例是扩展精度乘法,而不是只是相加。 intel.com/content/dam/www/public/us/en/documents/white-papers/… 表明 mulx 节省了一些指令,但 mulx + adcx / adox 为 512x512 或 512x64 位乘法节省更多,避免了 adc reg, 0add 之前保存进位的需要,因为您必须处理高/低半部分。重要的不是延迟,而是 uop 吞吐量和 OoO 窗口大小的指令总数。 (mulx 使用 p1+p5,adc/ox 使用 p06)。

以上是关于为啥clang++更喜欢adcx而不是adc的主要内容,如果未能解决你的问题,请参考以下文章

为啥游戏逻辑更喜欢 update() 而不是 didFinishUpdate?

为啥 JSLint 更喜欢点符号而不是方括号?

为啥 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?

为啥我应该更喜欢单个'await Task.WhenAll'而不是多个等待?

为啥 FetchContent 更喜欢子目录包含而不是安装依赖项?

为啥我们更喜欢在角度中使用 $q 而不是 $http [重复]