假设我想添加两个缓冲区并存储结果。两个缓冲区已经分配了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 );
}
}
所以问题是第二个例子是否正确或可能有任何副作用,何时使用加载/存储是强制性的。
感谢。
答案 0 :(得分:10)
两个版本都没问题 - 如果你看一下生成的代码,你会发现第二个版本仍然会向向量寄存器生成至少一个加载,因为PADDW
(又名_mm_add_epi16
)只能得到它的第二个参数直接来自记忆。
实际上,大多数非平凡的SIMD代码在加载和存储数据之间的操作比单个添加更多,因此通常您可能希望使用_mm_load_XXX
将数据最初加载到向量变量(寄存器) ,在寄存器上执行所有SIMD操作,然后通过_mm_store_XXX
将结果存储回存储器。
答案 1 :(得分:5)
主要区别在于,在第二个版本中,编译器将生成未对齐的加载(movdqu
等),如果它无法证明指针是16字节对齐的话。根据周围的代码,甚至可能无法编写编译器可以证明此属性的代码。
否则没有区别,编译器足够聪明,可以将两个加载和一个加载加入到内存中,如果它认为有用,或者将加载和添加指令拆分为两个。 / p>
如果您使用的是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,甚至可能被编译器扩展。
答案 2 :(得分:1)
至少使用gcc / clang,const res = fn(o)
{
A: {
B: {
C: [
{name: 'blah', value: 'vtha'}
{name: 'vtha', value: 'blah'},
]
}
}
}
与foo = *dst;
完全相同。按惯例,foo = _mm_load_si128(dst);
方式通常是首选,但对齐_mm_load_si128
的纯C / C ++解除引用也是安全的。
__m128i*
/ load
内在函数的主要目的是将对齐信息传递给编译器。
对于float / double,它们还在(loadu
)const
和float*
或(__m128
)const
&lt; - &gt;之间进行类型转换。 double*
。对于整数,你仍然需要自己投射:(。但是用AVX512内在函数修复了这个问题,其中整数加载/存储内在函数采用__m128d
args。
编译器仍然可以优化死存储或重新加载,并将加载折叠到ALU指令的内存操作数中。但是当它们实际上在它们的装配输出中发出存储或负载时,它们会以一种在输入源中保持对齐(或缺少对齐)的情况下不会出错的方式执行。
使用对齐的内部函数,编译器可以将加载折叠到具有SSE或AVX的ALU指令的内存操作数中。但是未对齐的加载内在函数只能用AVX折叠,因为SSE内存操作数类似于void*
加载。例如movdqa
可以使用AVX编译为_mm_add_epi16(xmm0, _mm_loadu_si128(rax))
,但SSE必须编译为vpaddw xmm0, xmm0, [rax]
/ movdqu xmm1, [rax]
。使用paddw xmm0, xmm1
代替load
可以避免使用SSE单独加载指令。
与C一样正常,取消引用loadu
被假定为对齐访问权限,例如__m128i*
或load_si128
。
在gcc&#39; store_si128
中,emmintrin.h
类型定义为__m128i
。
如果已使用__attribute__ ((__vector_size__ (16), __may_alias__ ))
,则gcc会将取消引用视为未对齐的访问权。