假设我想添加两个缓冲区并存储结果.两个缓冲区已经分配了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 );
}
}
Run Code Online (Sandbox Code Playgroud)
第二个例子直接在内存地址上执行了添加操作,没有加载/存储操作.两缝都很好.
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 );
}
}
Run Code Online (Sandbox Code Playgroud)
所以问题是第二个例子是否正确或可能有任何副作用,何时使用加载/存储是强制性的.
谢谢.
Pau*_*l R 11
两个版本都很好 - 如果你看一下生成的代码,你会发现第二个版本仍然会向向量寄存器生成至少一个加载,因为PADDW(aka _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];
}
Run Code Online (Sandbox Code Playgroud)
__v8hi是8个半整数的向量的缩写,或者typedef short __v8hi __attribute__ ((__vector_size__ (16)));每种向量类型都有类似的预定义类型,由gcc和icc支持.
这将产生几乎相同的代码,这可能会或可能不会更快.但有人可能会说它更具可读性,可以很容易地扩展到AVX,甚至可能是编译器.