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

时间:2017-05-27 13:10:11

标签: c++ x86 sse simd intrinsics

我正在寻找别人的代码,目前正试图找出_mm_load_si128存在的原因。

基本上,我试过替换

_ra = _mm_load_si128(reinterpret_cast<__m128i*>(&cd->data[idx]));

_ra = *reinterpret_cast<__m128i*>(&cd->data[idx]);

它的工作和执行完全一样。

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

_mm_load_si128还有其他什么吗?或者它本质上只是一种分配值的迂回方式?

1 个答案:

答案 0 :(得分:15)

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

  • _mm_load_si128(reinterpret_cast<__m128i*>(&cd->data[idx]));是显式加载
  • *reinterpret_cast<__m128i*>(&cd->data[idx]);是隐式加载

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

虽然作为扩展,大多数编译器也可以在执行type-punning时自动生成XMM加载,但这样您就无法控制加载是对齐还是未对齐。在这种情况下,由于在现代CPU上,当数据对齐时,使用未对齐的负载不会有性能损失,编译器倾向于普遍使用未对齐的负载。

另一个更重要的方面是,对于隐式加载,您违反了strict aliasing规则,这可能导致未定义的行为。虽然值得一提的是 - 作为扩展的一部分 - 支持英特尔内在函数的编译器不倾向于对XMM占位符类型强制执行严格的别名规则,如__m128__m128d__m128i

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

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

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

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

第二个原因(与第一个原因有些相关)在于这些类型的类型定义。

GCC在typedef<xmmintrin.h >中的SSE / SSE2占位符类型<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__));

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

现在,由于clangICC GCC 兼容,因此它们应遵循相同的约定。所以目前,在这3个编译器中,即使使用-fstrict-aliasing标志,隐式加载/存储也可以保证工作。最后,MSVC根本不支持严格的别名,所以它甚至不是一个问题。

但是,这并不意味着你应该更喜欢隐式加载/存储而不是显式加载/存储。