逻辑SSE内在函数之间有什么区别?

不同类型的逻辑SSE内在函数之间有什么区别吗? 例如,如果我们采用OR运算,有三个内在函数:_mm_or_ps,_mm_or_pd和_mm_or_si128所有这些都做同样的事情:计算其操作数的按位 OR。 我的问题:

  1. 使用一个或另一个内在(使用适当的类型转换)之间是否有任何区别。 在某些特定情况下,是否会有更长的执行等隐藏成本?

  2. 这些内在函数映射到三个不同的x86指令(por,orps,orpd)。 有没有人有任何想法为什么英特尔浪费宝贵的操作码空间的几个指令做同样的事情?

我认为这三者实际上是相同的,即128位按位运算。 不同forms存在的原因可能是历史性的,但我不确定。 我想在浮点版本中可能会有一些额外的行为,例如当有NaN时,但这是纯粹的猜测。 对于正常输入,指令似乎是可互换的,例如

#include  #include  #include  #include  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 
  1. 使用一个或另一个内在(使用适当的类型转换)之间是否有任何区别。 在某些特定情况下,是否会有更长的执行等隐藏成本?

是的,选择一个与另一个可能有性能原因。

1:如果整数执行单元的输出需要路由到FP执行单元的输入,或者反之亦然,则有时会有一个额外的周期或两个延迟(转发延迟)。 需要很多线才能将128b数据移动到许多可能的目的地中的任何一个,因此CPU设计人员必须进行权衡,例如只有从每个FP输出到每个FP输入的直接路径,而不是所有可能的输入。

请参阅此答案或Agner Fog的微架构文档,了解旁路延迟。 在Agner的doc中搜索“Nehalem上的数据绕过延迟”; 它有一些很好的实际例子和讨论。 他为每一个他分析过的微博都有一个部分。

但是,在Sandy Bridge和Ivy Bridge上,不同域或不同类型寄存器之间传递数据的延迟小于Nehalem,并且通常为零。 – Agner Fog的微拱文档

请记住,如果延迟不在代码的关键路径上,则延迟无关紧要。 如果pshufd吞吐量是你的瓶颈,而不是关键路径的延迟,那么使用pshufd代替movaps + shufps可能是一个胜利。

2: ...ps版本的代码字节比其他两个字节少1个字节。 这将以不同方式对齐以下指令,这对解码器和/或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上运行。 shuff还需要p5,但FMA,FP add和FP mul单元位于端口0/1上。

  • Skylake: pororps 都有3个周期的吞吐量 。 有关转发延迟的信息尚不可用。

请注意,在SnB / IvB(AVX但不是AVX2)上,只有p5需要处理256b逻辑运算,因为vpor ymm, ymm需要AVX2。 这可能不是改变的原因,因为Nehalem这样做了。

如何明智地选择

如果port5上的逻辑运算吞吐量可能成为瓶颈,则使用整数版本,即使在FP数据上也是如此。 如果要使用整数混洗或其他数据移动指令,尤其如此。

AMD CPU总是使用整数域作为逻辑,因此如果您有多个整数域要做的事情,请一次完成所有操作以最大限度地减少域之间的往返。 较短的延迟会更快地从重新排序缓冲区中清除,即使dep链不是代码的瓶颈。

如果您只想在FP add和mul指令之间设置/清除/翻转FP向量中的一位,请使用...ps逻辑,即使在双精度数据上,因为单FP和双FP在每个CPU上都是相同的域存在,而...ps版本缩短了一个字节。

但是,使用...pd版本存在实际/人为因素,这通常会超过保存1个字节的代码。 其他人对你的代码的可读性是一个因素:他们会想知道为什么你的数据实际上是双倍的时候将你的数据视为单身。 ESP。 使用C / C ++内在函数,在__mm256__mm256d之间使用强制转换乱丢你的代码是不值得的。 如果调整insn对齐的级别很重要,请直接写入asm,而不是内在函数! (将指令延长一个字节可能会使uop缓存行密度和/或解码器更好地对齐。)

对于整数数据,请使用整数版本。 保存一个指令字节不值得绕过延迟,整数代码通常会使port5完全被shuffle占用。 对于Haswell,许多shuffle / insert / extract / pack / unpack指令仅变为p5,而不是snB / IvB的p1 / p5。

  1. 这些内在函数映射到三个不同的x86指令( pororpsorpd )。 有没有人有任何想法为什么英特尔浪费宝贵的操作码空间的几个指令做同样的事情?

如果你看看这些指令集的历史,你可以看到我们如何到达这里。

 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被安排。 在function上它们产生相同的结果。 整数数据类型的较新版本具有较大的编码并占用代码段中的更多空间。 因此,如果代码大小是一个问题,请使用旧操作,因为它们具有较小的编码。