为什么SSE有128位负载功能?

use*_*993 13 c++ x86 sse simd intrinsics

我正在寻找其他人的代码,目前正试图弄清楚为什么_mm_load_si128存在.

基本上,我尝试更换

_ra = _mm_load_si128(reinterpret_cast<__m128i*>(&cd->data[idx]));
Run Code Online (Sandbox Code Playgroud)

_ra = *reinterpret_cast<__m128i*>(&cd->data[idx]);
Run Code Online (Sandbox Code Playgroud)

它的工作原理和表现完全相同.

我认为为了方便起见,较小类型存在加载函数,因此人们不必手动将它们打包到连续内存中,但对于已经按正确顺序排列的数据,为什么要这么麻烦?

还有别的_mm_load_si128吗?或者它本质上只是一种分配价值的迂回方式?

pla*_*cel 15

SSE中存在显式和隐式加载.

  • _mm_load_si128(reinterpret_cast<__m128i*>(&cd->data[idx])); 是一个明确的负载
  • *reinterpret_cast<__m128i*>(&cd->data[idx]); 是隐式加载

使用显式加载,您明确指示编译器将数据加载到XMM寄存器中 - 这是"正式"英特尔方式.您还可以使用_mm_load_si128或控制负载是对齐还是未对齐的负载_mm_loadu_si128.

虽然作为扩展,大多数编译器也能够在您进行打字时自动生成XMM加载,但这样您就无法控制加载是对齐还是未对齐.在这种情况下,由于在现代CPU上,在数据对齐时使用未对齐的负载没有性能损失,编译器倾向于普遍使用未对齐的负载.

另一个更重要的方面是,使用隐式加载会违反严格的别名规则,这可能导致未定义的行为.虽然这是值得一提的是-作为扩展的一部分-它支持英特尔内部函数不倾向于强制执行XMM占位符类型严格别名规则一样的编译器__m128,__m128d,__m128i.

尽管如此,我认为明确的载荷更清洁,更具防弹性.


为什么编译器不倾向于对SSE占位符类型强制执行严格的别名规则?

一个原因在于SSE内在函数的设计:当你必须使用类型惩罚时,有明显的情况,因为没有其他方法可以使用某些内在函数.Mysticial的回答完美地总结了它.

正如Cody Gray在评论中指出的那样,值得一提的是,历史上MMX教义(现在大部分被SSE2取代)甚至没有提供明确的加载或存储 - 你必须使用类型惩罚.

第二个原因(有些涉及到1)位于这些类型的类型定义.

GCC typedef用于SSE/SSE2占位符类型<xmmintrin.h ><emmintrin.h>:

/* The Intel API is flexible enough that we must allow aliasing with other
   vector types, and their scalar components.  */

typedef float __m128 __attribute__ ((__vector_size__ (16), __may_alias__));    
typedef long long __m128i __attribute__ ((__vector_size__ (16), __may_alias__));
typedef double __m128d __attribute__ ((__vector_size__ (16), __may_alias__));
Run Code Online (Sandbox Code Playgroud)

这里的关键是__may_alias__属性,即使在使用-fstrict-aliasing标志启用严格别名时,也会对这些类型进行类型处理.

现在,由于clangICCGCC兼容,它们应遵循相同的惯例.所以目前,在这3个编译器中,隐式加载/存储在某种程度上保证即使使用-fstrict-aliasingflag也可以工作.最后,MSVC根本不支持严格的别名,所以它甚至不是一个问题.

尽管如此,这并不意味着你应该更喜欢隐式加载/存储而不是显式加载/存储.

  • 您可以控制负载是对齐还是未对齐,例如参见非官方的`__m128i_u` typedef. (2认同)