- 使用一个或另一个内在函数(使用适当的类型转换)有什么区别。在某些特定情况下不会有任何隐性成本,例如更长的执行时间吗?
是的,选择一个与另一个可能有性能原因。
1: 如果整数执行单元的输出需要路由到 FP 执行单元的输入,有时会出现一两个额外的延迟周期(转发延迟),反之亦然.将 128b 的数据移动到许多可能的目的地中的任何一个都需要很多线,因此 CPU 设计人员必须做出权衡,例如只有从每个 FP 输出到每个 FP 输入的直接路径,而不是到所有可能的输入。
有关旁路延迟,请参阅 this answer 或 Agner Fog's microarchitecture doc。在 Agner 的文档中搜索“Nehalem 的数据绕过延迟”;它有一些很好的实际例子和讨论。对于他分析的每个微架构,他都有一个部分。
但是,在
不同的域或不同类型的寄存器在
Sandy Bridge 和 Ivy Bridge 比上 Nehalem,而且经常为零。 --
Agner Fog 的微拱文档
请记住,如果延迟不在代码的关键路径上(except sometimes on Haswell/Skylake where it infects later use of the produced value,实际绕过后很久:/),延迟并不重要。如果 uop 吞吐量是您的瓶颈,而不是关键路径的延迟,那么使用 pshufd 而不是 movaps + shufps 可能是一个胜利。
2:...ps 版本比其他两个用于传统 SSE 编码的代码少 1 个字节。 (不是 AVX)。这将以不同的方式对齐以下指令,这对于解码器和/或 uop 缓存线可能很重要。通常越小越好,因为 I-cache 和从 RAM 中获取代码并打包到 uop 缓存中的代码密度更高。
3:最近的 Intel CPU 只能在 port5 上运行 FP 版本。
-
Merom (Core2) 和 Penryn:orps 可以在 p0/p1/p5 上运行,但只能在整数域上运行。大概所有 3 个版本都解码为完全相同的 uop。所以就发生了跨域转发延迟。 (AMD CPU 也这样做:FP 位指令在 ivec 域中运行。)
-
Nehalem / Sandybridge / IvB / Haswell / Broadwell:por 可以在 p0/p1/p5 上运行,但 orps 只能在 port5 上运行。 shuffle 也需要 p5,但 FMA、FP add 和 FP mul 单元位于端口 0/1。
-
Skylake:por 和 orps both have 3-per-cycle throughput。英特尔的优化手册有一些关于绕过转发延迟的信息:往返 FP 指令取决于 uop 在哪个端口上运行。 (通常仍然是端口 5,因为 FP add/mul/fma 单元位于端口 0 和 1。)另请参阅 Haswell AVX/FMA latencies tested 1 cycle slower than Intel's guide says -“绕过”延迟会影响寄存器的每次使用,直到它被覆盖。
请注意,在 SnB/IvB(AVX 但不是 AVX2)上,只有 p5 需要处理 256b 个逻辑操作,因为vpor ymm, ymm 需要 AVX2。这可能不是改变的原因,因为 Nehalem 是这样做的。
如何明智地选择:
请记住,编译器可以根据需要将por 用于_mm_or_pd,因此其中一些主要适用于手写asm。但有些编译器在某种程度上忠实于您选择的内在函数。
如果端口 5 上的逻辑运算吞吐量可能成为瓶颈,则使用整数版本,即使是 FP 数据。如果您想使用整数随机播放或其他数据移动指令,则尤其如此。
AMD CPU 始终使用整数域进行逻辑运算,因此如果您有多个整数域的事情要做,请一次性完成所有任务,以最大限度地减少域之间的往返。更短的延迟将更快地从重新排序缓冲区中清除内容,即使 dep 链不是您的代码的瓶颈。
如果您只想在 FP add 和 mul 指令之间的 FP 向量中设置/清除/翻转位,请使用 ...ps 逻辑,即使在双精度数据上也是如此,因为单 FP 和双 FP 在每个CPU 存在,...ps 版本短一个字节(没有 AVX)。
不过,使用带有内在函数的 ...pd 版本有实际/人为因素的原因。其他人对您的代码的可读性是一个因素:他们会想知道为什么您将数据视为单数,而实际上它是双数的。对于 C/C++ 内在函数,用 __m128 和 __m128d 之间的强制转换乱扔代码是不值得的。 (希望编译器无论如何都会将orps 用于_mm_or_pd,如果在没有AVX 的情况下编译它实际上会节省一个字节。)
如果调整 insn 对齐的级别很重要,请直接用 asm 编写,而不是内在函数! (将指令延长一个字节可能会更好地对齐 uop 缓存线密度和/或解码器,但具有前缀和寻址模式you can extend instructions in general)
对于整数数据,请使用整数版本。保存一个指令字节不值得在paddd 或其他任何东西之间进行旁路延迟,并且整数代码通常使端口5完全被洗牌占据。对于 Haswell,许多 shuffle / insert / extract / pack / unpack 指令仅变为 p5,而不是 SnB/IvB 的 p1/p5。 (Ice Lake 终于在另一个端口上添加了一个 shuffle 单元,用于一些更常见的 shuffle。)
- 这些内在函数映射到三个不同的 x86 指令(
por、orps、
orpd)。有没有人知道为什么英特尔会浪费宝贵的操作码
为几条做同样事情的指令留出空间?
如果您查看这些指令集的历史,您就会明白我们是如何走到今天这一步的。
por (MMX): 0F EB /r
orps (SSE): 0F 56 /r
orpd (SSE2): 66 0F 56 /r
por (SSE2): 66 0F EB /r
MMX 在 SSE 之前就已存在,因此看起来 SSE (...ps) 指令的操作码是从同一 0F xx 空间中选择的。然后对于 SSE2,...pd 版本在 ...ps 操作码中添加了 66 操作数大小前缀,整数版本在 MMX 版本中添加了 66 前缀。
他们可能忽略了orpd 和/或por,但他们没有。也许他们认为未来的 CPU 设计可能在不同域之间有更长的转发路径,因此为您的数据使用匹配指令将是一件大事。即使有单独的操作码,AMD 和早期的 Intel 都将它们视为 int-vector。
相关/接近重复: