不同类型的逻辑SSE指令是否有区别?例如,如果我们考虑OR操作,有三种指令:_mm_or_ps
、_mm_or_pd
和_mm_or_si128
,它们都做同样的事情:计算它们的操作数的按位OR。我的问题如下:
使用其中一种指令(带适当的类型转换)与另一种指令是否有区别?在某些特定情况下是否会存在任何隐藏成本,例如执行时间变长等?
这些指令映射到三个不同的x86指令(
por
、orps
、orpd
)。有没有人知道为什么英特尔要浪费宝贵的操作码空间来实现几个做同样事情的指令?
不同类型的逻辑SSE指令是否有区别?例如,如果我们考虑OR操作,有三种指令:_mm_or_ps
、_mm_or_pd
和_mm_or_si128
,它们都做同样的事情:计算它们的操作数的按位OR。我的问题如下:
使用其中一种指令(带适当的类型转换)与另一种指令是否有区别?在某些特定情况下是否会存在任何隐藏成本,例如执行时间变长等?
这些指令映射到三个不同的x86指令(por
、orps
、orpd
)。有没有人知道为什么英特尔要浪费宝贵的操作码空间来实现几个做同样事情的指令?
- 使用不同的内置函数(带有适当的类型转换)之间是否有差异?在某些特定情况下,不会存在隐藏成本吗?
是的,在选择一个与另一个之间可能存在性能原因。
1:有时如果整数执行单元的输出需要路由到FP执行单元的输入,或者反之,则会有额外的延迟周期。将128b数据移动到任何可能的目标需要大量电线,因此CPU设计人员必须做出权衡,例如仅从每个FP输出到每个FP输入具有直接路径,而不是所有可能的输入。
请参见this answer或Agner Fog的微架构文档以获取旁路延迟信息。在Agner的文档中搜索“ Nehalem上的数据旁路延迟”,其中包含一些很好的实际示例和讨论。他对他分析的每个微体系结构都有一个相关部分。
然而,在Sandy Bridge和Ivy Bridge上,不同域或不同类型寄存器之间传递数据的延迟比Nehalem更小,通常为零。-- Agner Fog的微架构文档Merom (Core2)和Penryn: orps
可以在p0/p1/p5上运行,但仅限于整数域。假定所有3个版本都解码为完全相同的uop。因此发生跨域转发延迟。(AMD CPU也会这样做:FP位运算指令在ivec域中运行。)
Nehalem / Sandybridge / IvB / Haswell / Broadwell: por
可以在p0/p1/p5上运行,但orps
只能在port5上运行。 p5还需要洗牌,但FMA、FP加和FP乘单元在端口0/1上。
Skylake: por
和orps
每个周期都有3个吞吐量。英特尔的优化手册提供了一些关于旁路转发延迟的信息:对于/从FP指令,它取决于uop运行的端口。 (通常仍然是端口5,因为FP add/mul/fma单元在端口0和1上。) 另请参见Haswell AVX/FMA latencies tested 1 cycle slower than Intel's guide says - "bypass"延迟可能会影响寄存器的每次使用,直到被覆盖。
vpor ymm, ymm
需要AVX2。尽管Nehalem也能做到这一点,但这可能不是更改的原因。por
来代替_mm_or_pd
,因此其中一些内容仅适用于手写汇编代码。但是,某些编译器会相对忠实地使用您选择的内部函数。...ps
逻辑,即使在双精度数据上也是如此,因为单精度和双精度FP在现有的每个CPU上都是相同的域,而...ps
版本比较短(没有AVX)。...pd
版本更为实用。通过其他人阅读您的代码的可读性是一个因素:他们会想知道为什么您将数据处理为单精度浮点数时实际上是双精度浮点数。对于C/C++内部函数,在__m128
和__m128d
之间进行转换不值得。 (如果没有AVX编译,希望编译器将使用orps
来代替_mm_or_pd
,这样它实际上可以节省一个字节。)paddd
或其他内容之间绕过延迟,并且整数代码通常会保持端口5完全占用的洗牌。 对于Haswell,许多洗牌/插入/提取/打包/解包指令变为仅p5,而不是SnB / IvB的p1 / p5。 (Ice Lake最终在另一个端口上添加了一个混洗单元,用于一些常见的混洗。)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和早期英特尔将它们都视为int-vector。
相关/近似重复:
根据英特尔和AMD的优化指南,混合操作类型和数据类型会导致性能下降,因为CPU在内部为特定数据类型标记了64位寄存器的一半。这似乎主要影响流水线,因为指令被解码并安排uops。从功能上讲,它们产生相同的结果。整数数据类型的新版本具有更大的编码,并占用代码段中更多的空间。因此,如果代码大小是一个问题,请使用旧的操作,因为它们具有更小的编码。
我认为这三个都是相同的,即128位的按位操作。不同形式存在的原因可能是历史原因,但我不确定。我猜测浮点数版本可能会有一些额外的行为,例如在存在NaN时,但这只是猜测。对于正常输入,这些指令似乎是可互换的,例如:
#include <stdio.h>
#include <emmintrin.h>
#include <pmmintrin.h>
#include <xmmintrin.h>
int main(void)
{
__m128i a = _mm_set1_epi32(1);
__m128i b = _mm_set1_epi32(2);
__m128i c = _mm_or_si128(a, b);
__m128 x = _mm_set1_ps(1.25f);
__m128 y = _mm_set1_ps(1.5f);
__m128 z = _mm_or_ps(x, y);
printf("a = %vld, b = %vld, c = %vld\n", a, b, c);
printf("x = %vf, y = %vf, z = %vf\n", x, y, z);
c = (__m128i)_mm_or_ps((__m128)a, (__m128)b);
z = (__m128)_mm_or_si128((__m128i)x, (__m128i)y);
printf("a = %vld, b = %vld, c = %vld\n", a, b, c);
printf("x = %vf, y = %vf, z = %vf\n", x, y, z);
return 0;
}
终端:
$ gcc -Wall -msse3 por.c -o por
$ ./por
a = 1 1 1 1, b = 2 2 2 2, c = 3 3 3 3
x = 1.250000 1.250000 1.250000 1.250000, y = 1.500000 1.500000 1.500000 1.500000, z = 1.750000 1.750000 1.750000 1.750000
a = 1 1 1 1, b = 2 2 2 2, c = 3 3 3 3
x = 1.250000 1.250000 1.250000 1.250000, y = 1.500000 1.500000 1.500000 1.500000, z = 1.750000 1.750000 1.750000 1.750000
por
之后推出了orps
和稍后的orpd
。而SSE的物理基础从未发生过太大的变化。 - Potatoswattershufps
/ shufd
有 1c 旁路延迟,但在 Haswell 上没有。但是,作为一般规则,如果存在与周围相同数据类型的等效执行指令,请使用该指令。 - Noah