假设我想添加两个缓冲区并存储结果。两个缓冲区已经分配了16byte对齐。我找到了两个如何做到这一点的例子。
第一个是使用_mm_load将数据从缓冲区读入SSE寄存器,执行add操作并存储回结果寄存器。到现在为止,我会这样做。
void _add( uint16_t * dst, uint16_t const * src, size_t n )
{
for( uint16_t const * end( dst + n ); dst != end; dst+=8, src+=8 )
{
__m128i _s = _mm_load_si128( (__m128i*) src );
__m128i _d = _mm_load_si128( (__m128i*) dst );
_d = _mm_add_epi16( _d, _s );
_mm_store_si128( (__m128i*) dst, _d );
}
}
第二个例子直接在内存地址上执行了添加操作,没有加载/存储操作。两缝都很好。
void _add( uint16_t * dst, uint16_t const * src, size_t n )
{
for( uint16_t const * end( dst + n ); dst != end; dst+=8, src+=8 )
{
*(__m128i*) dst = _mm_add_epi16( *(__m128i*) dst, *(__m128i*) src );
}
}
所以问题是第二个例子是否正确或可能有任何副作用,何时使用加载/存储是强制性的。
谢谢。
两个版本都很好 - 如果你看一下生成的代码,你会发现第二个版本仍然会向向量寄存器生成至少一个加载,因为 PADDW
(又名 _mm_add_epi16
)只能直接从内存中获取第二个参数。
在实践中,大多数非平凡的SIMD代码将在加载和存储数据之间执行比单个添加更多的操作,因此通常您可能希望最初使用向量变量(寄存器)加载数据 _mm_load_XXX
,对寄存器执行所有SIMD操作,然后将结果存储回存储器 _mm_store_XXX
。
主要区别在于,在第二个版本中,编译器将生成未对齐的载荷( movdqu
如果它不能证明指针是16字节对齐的话。根据周围的代码,甚至可能无法编写编译器可以证明此属性的代码。
否则没有区别,编译器足够聪明,可以将两个加载和一个加载加入到内存中,如果它认为有用,或者将加载和添加指令拆分为两个,则加载到内存中。
如果你使用的是c ++,你也可以写
void _add( __v8hi* dst, __v8hi const * src, size_t n )
{
n /= 8;
for( int i=0; i<n; ++i )
d[i| += s[i];
}
__v8hi
是。的缩写 向量的8个半整数 要么 typedef short __v8hi __attribute__ ((__vector_size__ (16)));
,每个矢量类型都有类似的预定义类型,由gcc和icc支持。
这将导致几乎相同的代码,这可能会或可能不会更快。但有人可能会说它更具可读性,可以很容易地扩展到AVX,甚至可能是编译器。
至少有gcc / clang, foo = *dst;
与...完全相同 foo = _mm_load_si128(dst);
。该 _mm_load_si128
方法通常是按照惯例首选的,但是对齐的C / C ++解除引用 __m128i*
也很安全。
的主要目的 load
/loadu
内在函数是将对齐信息传递给编译器。
对于float / double,它们之间也是类型转换(const
) float*
和 __m128
要么 (const
) double*
< - > __m128d
。对于整数,你仍然需要自己投射:(。但是这已经被AVX512内在函数修复了,其中整数加载/存储内在函数采取 void*
ARGS。
编译器仍然可以优化死存储或重新加载,并将加载折叠到ALU指令的存储器操作数中。但是当它们实际上在它们的汇编输出中发出存储或负载时,它们会以一种不会因为源中的对齐保证(或缺少)而出错的方式执行。
使用对齐的内在函数,编译器可以使用SSE或AVX将负载折叠到ALU指令的内存操作数中。但是未对齐的加载内在函数只能用AVX折叠,因为SSE内存操作数就像 movdqa
负载。例如 _mm_add_epi16(xmm0, _mm_loadu_si128(rax))
可以编译成 vpaddw xmm0, xmm0, [rax]
使用AVX,但SSE必须编译到 movdqu xmm1, [rax]
/ paddw xmm0, xmm1
。一个 load
代替 loadu
可以让它避免使用SSE单独的加载指令。
对于C来说是正常的,取消引用a __m128i*
被假定是一个对齐的访问,就像 load_si128
要么 store_si128
。
在gcc中 emmintrin.h
, __m128i
type定义为 __attribute__ ((__vector_size__ (16), __may_alias__ ))
。
如果它已经使用过 __attribute__ ((__vector_size__ (16), __may_alias__, aligned(1) ))
,gcc会将取消引用视为未对齐访问。