为啥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。整个代码很长,所以我不知道如何发布它。
好吧,理论上adcx
和adox
可以是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, 0
在 add
之前保存进位的需要,因为您必须处理高/低半部分。重要的不是延迟,而是 uop 吞吐量和 OoO 窗口大小的指令总数。 (mulx
使用 p1+p5,adc/ox
使用 p06)。以上是关于为啥clang++更喜欢adcx而不是adc的主要内容,如果未能解决你的问题,请参考以下文章
为啥游戏逻辑更喜欢 update() 而不是 didFinishUpdate?
为啥 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?
为啥我应该更喜欢单个'await Task.WhenAll'而不是多个等待?