如何最佳实践地交换__m128i
变量?
背景是一个在Sun Studio 12.2下的编译错误,这是一个C++03编译器。 __m128i
是与MMX和SSE指令一起使用的不透明类型,通常是unsigned long long[2]
。 C ++03不支持交换数组,并且在编译器下std:swap(__m128i a, __m128i b)
会失败。
以下是一些相关问题,它们没有完全符合本题。它们并不适用,因为std::vector
不可用。
如何最佳实践地交换__m128i
变量?
背景是一个在Sun Studio 12.2下的编译错误,这是一个C++03编译器。 __m128i
是与MMX和SSE指令一起使用的不透明类型,通常是unsigned long long[2]
。 C ++03不支持交换数组,并且在编译器下std:swap(__m128i a, __m128i b)
会失败。
以下是一些相关问题,它们没有完全符合本题。它们并不适用,因为std::vector
不可用。
通过memcpy
进行交换?
#include <emmintrin.h>
#include <cstring>
template<class T>
void memswap(T& a, T& b)
{
T t;
std::memcpy(&t, &a, sizeof(t));
std::memcpy(&a, &b, sizeof(t));
std::memcpy(&b, &t, sizeof(t));
}
int main() {
__m128i x;
__m128i y;
memswap(x, y);
return 0;
}
__m128i tmp = a;
不能编译,那就相当糟糕。
__m128i
是一个POD类型,适合于单个矢量寄存器。不要做任何会鼓励编译器将其溢出到内存的事情。一些编译器甚至会为微不足道的测试用例生成非常可怕的代码,即使是gcc / clang也可能会因为优化大型复杂函数中的memcpy而出错。=
赋值进行复制。这在任何支持__m128i
的编译器中都可以高效地工作,并且是一种常见模式。_mm_store_si128
/ _mm_load_si128
一样:即movdqa
对齐存储/加载,如果在不对齐的地址上使用,则会失败。 (当然,优化可能导致加载折叠到另一个矢量指令的内存操作数中,或者根本不发生存储。)// alternate names: assignment_swap
// or swap128, but then the name doesn't fit for __m256i...
// __m128i t(a) errors, so just use simple initializers / assignment
template<class T>
void vecswap(T& a, T& b) {
// T t = a; // Apparently SunCC even choked on this
T t;
t = a;
a = b;
b = t;
}
-O3
的汇编输出。__m128i test_return2nd(__m128i x, __m128i y) {
vecswap(x, y);
return x;
}
movdqa xmm0, xmm1
ret # returning the 2nd arg, which was in xmm1
__m128i test_return1st(__m128i x, __m128i y) {
vecswap(x, y);
return y;
}
ret # returning the first arg, already in xmm0
return1st_memcpy(__m128i, __m128i): ## ICC13 -O3
movdqa XMMWORD PTR [-56+rsp], xmm0
movdqa XMMWORD PTR [-40+rsp], xmm1 # spill both
movaps xmm2, XMMWORD PTR [-56+rsp] # reload x
movaps XMMWORD PTR [-24+rsp], xmm2 # copy x to tmp
movaps xmm0, XMMWORD PTR [-40+rsp] # reload y
movaps XMMWORD PTR [-56+rsp], xmm0 # copy y to x
movaps xmm0, XMMWORD PTR [-24+rsp] # reload tmp
movaps XMMWORD PTR [-40+rsp], xmm0 # copy tmp to y
movdqa xmm0, XMMWORD PTR [-40+rsp] # reload y
ret # return y
memcpy
之间进行优化,甚至不记得寄存器中剩下了什么。
// the memcpy version of this compiles badly
void test_mem(__m128i *x, __m128i *y) {
vecswap(*x, *y);
}
# gcc 5.3 and ICC13 make the same code here, since it's easy to optimize
movdqa xmm0, XMMWORD PTR [rdi]
movdqa xmm1, XMMWORD PTR [rsi]
movaps XMMWORD PTR [rdi], xmm1
movaps XMMWORD PTR [rsi], xmm0
ret
// gcc 5.3 with memswap instead of vecswap. ICC13 is similar
test_mem_memcpy(long long __vector(2)*, long long __vector(2)*):
mov rax, QWORD PTR [rdi]
mov rdx, QWORD PTR [rdi+8]
mov r9, QWORD PTR [rsi]
mov r10, QWORD PTR [rsi+8]
mov QWORD PTR [rdi], r9
mov QWORD PTR [rdi+8], r10
mov QWORD PTR [rsi], rax
mov QWORD PTR [rsi+8], rdx
ret
vecswap
,这样您就可以在派生类型上使用它,例如Agner Fog的矢量类库包装器,如Vec4i
。如果您进行专门化,您可能需要对__m128i
、__m128d
、__m128
、__m256*
、__m512*
和其他架构的SIMD类型进行专门化,(如ARM NEON或其他)。这很笨重。我更愿意使用一个不同的函数,我确信它总是轻量级的,并且能够良好地优化。如果您确定只需要少数类型,则值得考虑进行专门化。 - Peter CordesT t = a; a = b; b = t;
。我不得不使用 T t; t=a, a=b, b=t;
来让编译器停止尝试初始化它。 - jww__m128i v = _mm_add_epi32(a,b);
这一行卡住?对于大多数代码,我认为我会考虑它已经坏了,而不是为了它的好处重写任何重要的代码。 - Peter Cordesvecswap()
和 std::swap()
有什么不同? - Leon
__m128i
值是最优的。gcc确实可以优化掉memcpy并将值保留在寄存器中(使用一个接受两个__m128i
参数并返回__m128i
的测试函数),但ICC13则不行。简单的赋值对优化的负面影响要小得多。例如,ICC13没有问题。 - Peter Cordes__m128i
上使用memswap
时,如果这些值已经在内存中,也可能会出现问题。请参阅我的答案。 - Peter Cordes