我正在寻找其他人的代码,目前正试图找出原因 _mm_load_si128
存在。
基本上,我尝试更换
_ra = _mm_load_si128(reinterpret_cast<__m128i*>(&cd->data[idx]));
同
_ra = *reinterpret_cast<__m128i*>(&cd->data[idx]);
它的工作原理和表现完全相同。
我认为为了方便起见,较小类型存在加载函数,因此人们不必手动将它们打包到连续内存中,但对于已经按正确顺序排列的数据,为什么要这么麻烦?
还有别的吗? _mm_load_si128
呢?或者它本质上只是一种分配价值的迂回方式?
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
s中的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 根本不支持严格的别名,所以它甚至不是一个问题。
尽管如此,这并不意味着你应该更喜欢隐式加载/存储而不是显式加载/存储。