_mm_set_epi64
和类似的*_epi64指令似乎使用并依赖于__m64
类型。我想初始化一个__m128
类型的变量,使其上64位为0,下64位设置为x
,其中x
是uint64_t
(或类似的无符号64位类型)。有没有“正确”的方法来做到这一点?最好以独立于编译器的方式实现。
_mm_set_epi64
和类似的*_epi64指令似乎使用并依赖于__m64
类型。我想初始化一个__m128
类型的变量,使其上64位为0,下64位设置为x
,其中x
是uint64_t
(或类似的无符号64位类型)。有没有“正确”的方法来做到这一点?_mm_loadl_epi64(&x)
将完全符合你的要求。_mm_set_epi64
,我曾经说过,查看Agner Fog的向量类库的源代码可以回答95%的SSE / AVX问题。 Agner为多个编译器和64位和32位实现了这个(来自文件vectori128.h)。请注意,对于MSVC 32位的解决方案,Agner表示“这很低效,但其他解决方案更糟”。我想这就是Mysticial所说的“没有好的方法可行”的意思。Vec2q(int64_t i0, int64_t i1) {
#if defined (_MSC_VER) && ! defined(__INTEL_COMPILER)
// MS compiler has no _mm_set_epi64x in 32 bit mode
#if defined(__x86_64__) // 64 bit mode
#if _MSC_VER < 1700
__m128i x0 = _mm_cvtsi64_si128(i0); // 64 bit load
__m128i x1 = _mm_cvtsi64_si128(i1); // 64 bit load
xmm = _mm_unpacklo_epi64(x0,x1); // combine
#else
xmm = _mm_set_epi64x(i1, i0);
#endif
#else // MS compiler in 32-bit mode
union {
int64_t q[2];
int32_t r[4];
} u;
u.q[0] = i0; u.q[1] = i1;
// this is inefficient, but other solutions are worse
xmm = _mm_setr_epi32(u.r[0], u.r[1], u.r[2], u.r[3]);
#endif // __x86_64__
#else // Other compilers
xmm = _mm_set_epi64x(i1, i0);
#endif
};
最常见的用于此的“标准”内部函数是_mm_set_epi64x。
对于缺少_mm_set_epi64x
的平台,您可以定义一个替换宏,如下所示:
#define _mm_set_epi64x(m0, m1) _mm_set_epi64(_m_from_int64(m0), _m_from_int64(m1))
_mm_set_epi32()
函数。 - Mysticial__m128
类型的变量 ...其中x是uint64_t
类型。uint64_t
的内置函数_mm_set_epi64x
(而不是使用_mm_set_epi64
,它需要一个__m64
)。_mm_set_epi64x
。 它也缺少像_mm_cvtsi64_si128
和_m_from_int64
这样的解决方法。// Sun Studio 12.3 and earlier lack SSE2's _mm_set_epi64 and _mm_set_epi64x.
#if defined(__SUNPRO_CC) && (__SUNPRO_CC < 0x5130)
inline __m128i _mm_set_epi64x(const uint64_t a, const uint64_t b)
{
union INT_128_64 {
__m128i v128;
uint64_t v64[2];
};
INT_128_64 v;
v.v64[0] = b; v.v64[1] = a;
return v.v128;
}
#endif
我相信C++11可以做更多的事情来帮助编译器和提高性能,比如初始化一个常量数组:
const INT_128_64 v = {a,b};
return v.v128;
v64
成员进行写入,然后使用v128
成员进行读取。在SunCC下测试表明编译器正在执行预期的(但技术上不正确的)操作。memcpy
来规避未定义的行为,但这可能会影响性能。还请参阅Peter Cordes在How to swap two __m128i variables in C++03 given its an opaque type and an array?中的回答和讨论。INT_128_64 v;
v.v64[0] = b; v.v64[1] = a;
return *(reinterpret_cast<__m128i*>(v.v64));
EDIT(三个月后):Solaris和SunCC不喜欢这种玩笑。它为我们生成了糟糕的代码,我们不得不将值复制到__m128i
中。Unix、Linux、Windows、GCC、Clang、ICC、MSC都没有问题。只有SunCC给我们带来了麻烦。
__m128
类型,它们带有may_alias属性或类似属性。希望SunCC也能类似地定义它。) - Peter Cordesmemcpy
是唯一由ISO C和C++保证可移植的类型转换技术,但基于联合体的类型转换在实际生活中被广泛使用。 - Peter Cordes_mm_loadl_pi
,它可能是一个合适的替代品。它允许加载未对齐的64位值。使用 _mm_loadl_pi
两次并进行中间移位可能可以避免一些理论上的问题。 - jwwmovd xmm0, eax
或 movq xmm0, rax
或其他类似的指令,那么也许可以考虑使用 _mm_loadu_si64 (void const* mem_addr)
这个内部函数(用于将低 64 位加载到 xmm 寄存器中,上 64 位清零)。或者在不支持该函数的编译器上,可以使用 _mm_loadl_epi64
函数,它也会被编译成一个 movq
指令。然后再使用 _mm_loadh_pi
函数将上半部分加载到 xmm 寄存器中,使用移位内部函数是愚蠢的,绝对不要使用两次! - Peter Cordesmovq
/ movl/h
对于一对非相邻的64位值可能是很好的选择。顺便说一下,没有指令要求64位或更小的操作数对齐;你不需要一个特殊的内部函数来实现这个。我也可以避免在一对相邻的最近写入的64位值上出现存储转发失败停顿,而是使用 _mm_loadu_si128
。(但是 _mm_loadu
的指针转换应该是别名安全的。可能你的转换版本实际上是安全的,但我认为我曾经看到过一个SO问题,类似的东西并没有做到OP想要的。) - Peter Cordes*(__m128i*)&v64[0]
强制转换应该是安全的,因为 __m128 类型是特殊的,允许别名,不像 *(double *)&v64[0]
。例如,gcc使用 __attribute__((may_alias))
定义 __m128i,而不是它所基于的内部 v2qi 本地向量类型。我从来不喜欢使用 memcpy
进行类型转换,但也许编译器已经足够好了,可以看穿它,以至于你不会在实践中得到糟糕的代码。我忘记是否曾经见过它没有被优化掉的例子。 - Peter Cordes