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


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

基本上,我尝试更换

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

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

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

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

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


5076
2018-05-27 13:10


起源

它可能是(或扩展到)一些内置的编译器。您使用的是什么C ++编译器? - Basile Starynkevitch
@BasileStarynkevitch是视觉工作室附带的那个 - user81993
是不是 _ra = reinterpret_cast<__m128>(cd->data[idx]) 还有可能? - Walter
@Walter不,你不能用 reinterpret_cast 对象,即使它们是占位符。看到 en.cppreference.com/w/cpp/language/reinterpret_cast - plasmacel
@plasmacel正确。但是,一个,可以使用演员表参考。 - Walter


答案:


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的 typedefs中的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__));

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

现在,从那以后  和 ICC 兼容 GCC,他们应该遵循同样的惯例。所以目前,在这3个编译器中,隐式加载/存储在某种程度上保证可以正常工作 -fstrict-aliasing 旗。最后, MSVC 根本不支持严格的别名,所以它甚至不是一个问题。

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


13
2018-05-27 13:20



这个答案的关键组成部分是严格别名 - 显式加载避免未定义的行为。对于支持Intel内在函数的编译器没有对XMM类型强制执行严格的别名规则,或者仅仅基于您自己的经验,您是否有某种类型的参考?我问,因为它也符合我的经验,但仅仅因为某些工作并不意味着它不会冒UB的风险! - Cody Gray♦
还可以补充说,这些显式加载对于SSE来说是新的。它们不是由MMX内在函数提供的,它基本上是隐式加载和丑陋的演员必不可少的 所有 加载操作。 - Cody Gray♦
@CodyGray这种行为与英特尔内在函数没有正式关联,但是当内在函数的设计迫使你使用别名时有明显的情况 - 没有别的办法。我推荐这个答案: stackoverflow.com/a/24788226/2430597。 - plasmacel
您可以控制负载是对齐还是未对齐,例如参见非官方的 __m128i_u 的typedef。 - Marc Glisse
取消引用时,gcc / clang使用对齐的加载 __m128i*。如果你要求,你只会得到不对齐。 (我假设即使是ICC和MSVC仍会将这些负载折叠到SSE指令的存储器操作数中,例如 paddd xmm0, [rsi],这也会给他们一个对齐要求。如果ICC / MSVC决定使用单独的加载指令(如。),则在没有AVX的情况下,您可能只会获得未对齐的加载 movdqu))。 - Peter Cordes