SSE:_mm_load /存储之间的差异与使用直接指针访问(SSE: Difference bet

2019-07-29 21:56发布

假设我想补充两个缓冲区,并存储结果。 两个缓冲区已经分配16字节对齐。 我发现了两个例子来说明如何做到这一点。

第一种是使用_mm_load从缓冲器中的数据读入寄存器SSE,确实增加操作,并存储回结果寄存器。 到现在为止我会做这样的。

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 );
  }
}

所以现在的问题是,如果第二个例子是正确的或可能有任何的副作用以及何时使用加载/存储是强制性的。

谢谢。

Answer 1:

这两个版本都很好-如果你看一下生成的代码,你会看到第二个版本仍然会产生至少一个负载向量寄存器,因为PADDW (又名_mm_add_epi16 )只能直接从内存获取它的第二个参数。

在实践中,大多数不平凡的SIMD代码将执行加载和存储不仅仅是一个单一的附加数据之间有更多的操作,所以一般你可能想使用初始数据加载到矢量变量(寄存器) _mm_load_XXX ,执行所有的SIMD操作寄存器,然后通过存储结果返回到存储器_mm_store_XXX



Answer 2:

主要的区别是,在第二个版本,编译器会生成未对齐负载( 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。



Answer 3:

用gcc /铛至少, foo = *dst; 是完全一样的foo = _mm_load_si128(dst); 。 的_mm_load_si128方式通常是通过常规优选的,但对准的普通的C / C ++解除引用__m128i*也是安全的。


所述的主要目的load / loadu内在是对准信息传送给编译器。

浮法/双,他们还类型转换(间constfloat*__m128或( constdouble* < - > __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是正常的,解引用__m128i*假定为对齐的访问,如load_si128store_si128

在gcc的emmintrin.h ,所述__m128i类型与定义__attribute__ ((__vector_size__ (16), __may_alias__ ))

如果曾使用__attribute__ ((__vector_size__ (16), __may_alias__, aligned(1) )) ,GCC会像对待一个解引用作为对齐访问。



文章来源: SSE: Difference between _mm_load/store vs. using direct pointer access
标签: x86 sse simd