16 c sse simd intrinsics sse2
不同类型的逻辑SSE内在函数之间有什么区别吗?例如,如果我们采用OR运算,有三个内在函数:_mm_or_ps,_mm_or_pd和_mm_or_si128所有这些都做同样的事情:计算其操作数的按位 OR.我的问题:
使用一个或另一个内在(使用适当的类型转换)之间是否有任何区别.在某些特定情况下,是否会有更长的执行等隐藏成本?
这些内在函数映射到三个不同的x86指令(por,orps,orpd).有没有人有任何想法为什么英特尔浪费宝贵的操作码空间的几个指令做同样的事情?
Pet*_*des 12
- 使用一个或另一个内在(使用适当的类型转换)之间是否有任何区别.在某些特定情况下,是否会有更长的执行等隐藏成本?
是的,选择一个与另一个可能有性能原因.
1:如果整数执行单元的输出需要路由到FP执行单元的输入,或者反之亦然,则有时会有一个额外的周期或两个延迟(转发延迟).需要很多线才能将128b数据移动到许多可能的目的地中的任何一个,因此CPU设计人员必须进行权衡,例如只有从每个FP输出到每个FP输入的直接路径,而不是所有可能的输入.
请参阅此答案,或Agner Fog的微架构文档,了解旁路延迟.在Agner的文档中搜索"Nehalem上的数据绕过延迟"; 它有一些很好的实际例子和讨论.他为每一个他分析过的微博都有一个部分.
但是,在Sandy Bridge和Ivy Bridge上,不同域或不同类型寄存器之间传递数据的延迟小于Nehalem,并且通常为零. - Agner Fog的微拱文档
请记住,如果延迟不在代码的关键路径上,则延迟无关紧要.如果uop吞吐量是你的瓶颈,而不是关键路径的延迟,那么使用pshufd而不是movaps + shufps赢.
2:该...ps版本占用的代码字节少于其他两个字节.这将以不同方式对齐以下指令,这对解码器和/或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微架构:por和orps 都有3 -每个周期的吞吐量.有关转发延迟的信息尚不可用.
请注意,在SnB/IvB(AVX但不是AVX2)上,只有p5需要处理256b逻辑运算,因为vpor ymm, ymm需要AVX2.这可能不是改变的原因,因为Nehalem这样做了.
如何明智地选择:
如果port5上的逻辑运算吞吐量可能成为瓶颈,则使用整数版本,即使在FP数据上也是如此.如果要使用整数shuffle或其他数据移动指令,尤其如此.
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,而不是sn1/IvB的p1/p5.
- 这些内在映射到三个不同的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
Run Code Online (Sandbox Code Playgroud)
MMX在SSE之前就已存在,因此看起来SSE(...ps)指令的操作码是从同一0F xx空间中选择的.然后对于SSE2,...pd版本为66操作...ps码添加了操作数大小前缀,整数版本66为MMX版本添加了前缀.
他们可能会遗漏orpd和/或por,但他们没有.也许他们认为未来的CPU设计可能在不同域之间有更长的转发路径,因此使用匹配的数据指令将是一个更大的交易.即使有单独的操作码,AMD和早期英特尔也会像int-vector一样对待它们.
根据英特尔和AMD的优化指南,将操作类型与数据类型混合会产生性能损失,因为CPU内部标记了特定数据类型的64位寄存器.这似乎主要影响管道衬里,因为指令被解码并且uop被安排.在功能上它们产生相同的结果.整数数据类型的较新版本具有较大的编码并占用代码段中的更多空间.因此,如果代码大小是一个问题,请使用旧操作,因为它们具有较小的编码.
我认为这三个实际上是相同的,即 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;
}
Run Code Online (Sandbox Code Playgroud)
终端:
$ 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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3605 次 |
| 最近记录: |