在整数向量上使用 _mm_shuffle_ps 的含义

Posted

技术标签:

【中文标题】在整数向量上使用 _mm_shuffle_ps 的含义【英文标题】:implications of using _mm_shuffle_ps on integer vector 【发布时间】:2014-11-17 22:59:03 【问题描述】:

SSE 内在函数包括_mm_shuffle_ps xmm1 xmm2 immx,它允许从xmm1 中选择2 个元素,并与xmm2 中的2 个元素连接。然而,这适用于浮点数,(由 _ps 暗示,打包单个)。但是,如果您转换压缩整数 __m128i,那么您也可以使用 _mm_shuffle_ps

#include <iostream>
#include <immintrin.h>
#include <sstream>

using namespace std;

template <typename T>
std::string __m128i_toString(const __m128i var) 
    std::stringstream sstr;
    const T* values = (const T*) &var;
    if (sizeof(T) == 1) 
        for (unsigned int i = 0; i < sizeof(__m128i); i++) 
            sstr << (int) values[i] << " ";
        
     else 
        for (unsigned int i = 0; i < sizeof(__m128i) / sizeof(T); i++) 
            sstr << values[i] << " ";
        
    
    return sstr.str();




int main()

  cout << "Starting SSE test" << endl;
  cout << "integer shuffle" << endl;

 int A[] = 1,  -2147483648, 3, 5;
 int B[] = 4, 6, 7, 8;

  __m128i pC;

  __m128i* pA = (__m128i*) A;
  __m128i* pB = (__m128i*) B;

  *pA = (__m128i)_mm_shuffle_ps((__m128)*pA, (__m128)*pB, _MM_SHUFFLE(3, 2, 1 ,0));
  pC = _mm_add_epi32(*pA,*pB);

  cout << "A[0] = " << A[0] << endl;
  cout << "A[1] = " << A[1] << endl;
  cout << "A[2] = " << A[2] << endl;
  cout << "A[3] = " << A[3] << endl;

  cout << "B[0] = " << B[0] << endl;
  cout << "B[1] = " << B[1] << endl;
  cout << "B[2] = " << B[2] << endl;
  cout << "B[3] = " << B[3] << endl;

  cout << "pA = " << __m128i_toString<int>(*pA) << endl;
  cout << "pC = " << __m128i_toString<int>(pC) << endl;

相关对应程序集的片段(mac osx、macports gcc 4.8、-march=native on an ivybridge CPU):

vshufps $228, 16(%rsp), %xmm1, %xmm0
vpaddd  16(%rsp), %xmm0, %xmm2
vmovdqa %xmm0, 32(%rsp)
vmovaps %xmm0, (%rsp)
vmovdqa %xmm2, 16(%rsp)
call    __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
....

因此,它似乎在整数上运行良好,这是我所期望的,因为寄存器与类型无关,但是文档说该指令仅适用于浮点数肯定是有原因的。有人知道我遗漏的任何缺点或影响吗?

【问题讨论】:

访问具有不兼容类型的 SSE/AVX 寄存器可能会损害性能。 (仅在最新的英特尔处理器 AFAIK 上) 查看这个问题difference-between-the-avx-instructions-vxorpd-and-vpxor中的cmets。尤其是 Mysticial 的第一个 mm-shuffle-ps-equivalent-for-integer-vectors-m128i. 我知道类似的问题,虽然问题是有一个等效的功能,但我的问题以 shuffle 指令为例。 【参考方案1】:

对于整数,没有等价于_mm_shuffle_ps。要在这种情况下达到相同的效果,您可以这样做

SSE2

*pA = _mm_shuffle_epi32(_mm_unpacklo_epi32(*pA, _mm_shuffle_epi32(*pB, 0xe)),0xd8);

SSE4.1

*pA = _mm_blend_epi16(*pA, *pB, 0xf0);

或change to the floating point domain 像这样

*pA = _mm_castps_si128( 
        _mm_shuffle_ps(_mm_castsi128_ps(*pA), 
                       _mm_castsi128_ps(*pB), _MM_SHUFFLE(3, 2, 1 ,0)));

But changing domains may incur bypass latency delays 在某些 CPU 上。请记住,根据 Agner 的说法

旁路延迟在延迟是瓶颈的长依赖链中很重要,但是 重要的是吞吐量而不是延迟。

你必须测试你的代码,看看上面哪种方法更有效。

幸运的是,在大多数 Intel/AMD CPU 上,在大多数整数向量指令之间使用 shufps 通常不会受到惩罚。阿格纳说:

例如,我发现在混合PADDDSHUFPS [on Sandybridge] 时没有延迟。

Nehalem 确实有 2 个与 SHUFPS 之间的旁路延迟延迟,但即便如此,单个 SHUFPS 通常仍然比多个其他指令快。额外的指令也有延迟,并且会消耗吞吐量。


反过来(FP 数学指令之间的整数洗牌)并不安全:

在示例 8.3a 的第 112 页的 Agner Fog's microarchitecture 中,他表明在浮点域中使用 PSHUFD (_mm_shuffle_epi32) 而不是 SHUFPS (_mm_shuffle_ps) 会导致四个时钟的旁路延迟循环。在示例 8.3b 中,他使用 SHUFPS 来消除延迟(在他的示例中有效)。

在 Nehalem 上实际上有五个域。 Nahalem 似乎是受影响最大的(在 Nahalem 之前不存在旁路延迟)。在桑迪桥上,延误不那么严重。在 Haswell 上更是如此。事实上,在 Haswell Agner 上,他发现 SHUFPSPSHUFD 之间没有延迟(参见第 140 页)。

【讨论】:

我已经更新了我的示例代码并添加了一个已生成程序集的 sn-p。所以,如果你有例如vshufps $228, 16(%rsp), %xmm1, %xmm0;vpaddd 16(%rsp), %xmm0, %xmm2 其中 vhufps 是 FLOAT 域而 VPADDD 是在 INT 域中,然后 CPU 将在内部进行绕过,因此至少对于 xmm0 而言是这样的? @hbogert,对不起,我没有引起足够的重视。 _mm_shuffle_ps_mm_shuffle_epi32 不等效。我更新了我的答案。 @hbogert,您可以使用 *pA = _mm_blend_epi16(*pA, *pB, 0xf0); 对 SSE4.1 执行此操作 你能回答我上面的3条评论吗?这是会发生延误的例子吗?如果这是正确的,我的问题(我承认有点含糊)得到了回答。 @hbogert,是的,据我了解,这种情况可能会导致绕过延迟。这就是为什么我展示了不同的方法。你可以试试_mm_blend_epi16,这不会像我建议的那样造成延迟,看看它是否有所作为。但是您的循环可能需要高度优化才能注意到差异。

以上是关于在整数向量上使用 _mm_shuffle_ps 的含义的主要内容,如果未能解决你的问题,请参考以下文章

在 Xcode 4 (LLVM GCC) 中使用 _mm_shuffle_ps 时可能出现 OpenMP + SSE 错误

可以从一个来源获取 3 个元素的 _mm_shuffle_ps 的替代方案?

在 __m128i 向量上水平检查零?

SSE 的整数/浮点值

使用 192/256 位整数对无符号 64 位整数向量的点积求和的最快方法?

使用 intel 内在函数将压缩的 8 位整数乘以浮点向量