为什么 clang 发出 32 位 float ps 指令来获取 64 位 double 的绝对值?

2024-03-17

clang为什么会转fabs(double) into vandps代替vandpd(就像海湾合作委员会那样)?


示例来自编译器资源管理器 https://gcc.godbolt.org/z/TsfW9hrjT:

#include <math.h>

double float_abs(double x) {
    return fabs(x);
}

铿锵12.0.1-std=gnu++11 -Wall -O3 -march=znver3

.LCPI0_0:
        .quad   0x7fffffffffffffff              # double NaN
        .quad   0x7fffffffffffffff              # double NaN
float_abs(double):                          # @float_abs(double)
        vandps  xmm0, xmm0, xmmword ptr [rip + .LCPI0_0]
        ret

海湾合作委员会11.2-std=gnu++11 -Wall -O3 -march=znver3

float_abs(double):
        vandpd  xmm0, xmm0, XMMWORD PTR .LC0[rip]
        ret
.LC0:
        .long   -1
        .long   2147483647
        .long   0
        .long   0

(讽刺的是,GCC 使用vandpd但用 32 位定义常量.long块(有趣的是上半部分为零),而 clang 使用vandps但将常数定义为两个.quad halves.


TL:DR:可能是因为优化器/代码生成器总是更容易执行此操作,而不是仅使用旧版 SSE 指令来节省代码大小。没有性能方面的缺点,而且它们在架构上是等效的(即没有正确性差异。)


也许 clang 总是将架构上等效的指令“规范化”为它们的指令ps版本,因为这些版本对于旧版 SSE 版本具有较短的机器代码编码。

No existing x86 CPUs have any bypass delay latency for forwarding between ps and pd instructions1, so it's always safe to use [v]andps between [v]mulpd or [v]fmadd...pd instructions.

As orpd等SSE2指令有什么意义? https://stackoverflow.com/q/62111946指出,指令如movupd and andpd是完全无用的空间浪费,仅用于解码器一致性:66SSE1 操作码前面的前缀始终是它的 pd 版本。为未来的其他扩展节省一些编码空间可能更明智,但英特尔没有这样做。

或者也许动机是未来 CPU 的可能性did具有单独的 SIMD-double 域和 SIMD-float 域,因为当 SSE2 在纸面上设计时,英特尔 FP SIMD 还处于早期阶段。如今,我们可以说这不太可能,因为 FMA 单元需要大量晶体管,并且显然可以构建为在每个 64 位元素一个 53 位尾数与每个 2x 32- 两个 23 位尾数之间共享一些尾数乘法器硬件。位元素。

拥有单独的转发域可能只有在您还具有用于浮点与双精度数学的单独执行单元而不共享晶体管的情况下才有用,除非您对不同类型有不同的输入和输出端口但实际内部结构相同?我对 CPU 设计细节的级别已经足够了解了。


没有什么优势可言ps对于 AVX VEX 编码版本,但也没有缺点,因此对于 LLVM 的优化器来说可能更简单/ 代码生成器总是这样做,而不是关心尝试尊重源内部函数。 (Clang / LLVM 通常不会尝试这样做,例如,它可以自由地将 shuffle 内在函数优化为不同的 shuffle。通常这很好,但有时,当它不知道作者的技巧时,它会取消优化精心设计的内在函数内在函数做到了。)

例如LLVM 可能会按照“FP 域 128 位按位与”来思考,并且知道其指令是andps / vandps。 clang 没有理由知道这一点vandpd存在,因为没有任何情况可以帮助使用它。


脚注 1:推土机隐藏元数据和数学指令之间的转发:
AMD Bulldozer 系列会因一些无意义的事情而受到惩罚,例如mulps -> mulpd,对于实际 FPmath实际上关心 FP 值的符号/指数/尾数部分的指令(不是布尔值或洗牌)。

将两个 IEEE 二进制 32 FP 值的串联视为二进制 64 基本上没有任何意义,因此这不是一个需要解决的问题。它主要只是让我们深入了解 CPU 内部结构的设计方式。

在推土机系列部分Agner Fog 的微架构指南 https://agner.org/optimize/,他解释说,在 FMA 单元上运行的两条数学指令之间转发的旁路延迟比另一条指令妨碍时低 1 个周期。例如addps / orps / addps延迟比addps / addps / orps,假设这三个指令形成依赖链。

但对于像这样疯狂的事情addps / addpd / orps,你会得到额外的延迟。但不是为了addps / orps / addpd. (orps vs orpd这里从来没有什么区别。shufps也将是等价的。)

可能的解释是,BD 保留了向量元素的额外内容,以便在特殊转发情况下重用,以便在转发 FMA->FMA 时避免一些格式化/标准化工作。如果格式错误,乐观方法必须恢复并执行架构所需的操作,但同样,只有当您实际上将浮点 FMA/add/mul 的结果视为双精度数时才会发生这种情况,反之亦然。

addps可以转发到像这样的随机播放unpcklpd毫不拖延,因此这不是 3 个独立旁路网络的证据,也不是使用(或存在)的任何理由andpd / orpd.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 clang 发出 32 位 float ps 指令来获取 64 位 double 的绝对值? 的相关文章

随机推荐