【问题标题】:Why are there 128bit load functions for SSE?为什么 SSE 有 128 位加载功能?
【发布时间】:2017-10-28 06:34:50
【问题描述】:

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

基本上,我尝试替换

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

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

它的工作原理和执行完全相同。

我认为对于较小的类型存在加载函数只是为了方便,因此人们不必手动将它们打包到连续内存中,但对于已经按正确顺序的数据,何必呢?

_mm_load_si128 还有其他功能吗?或者它本质上只是一种迂回的赋值方式?

【问题讨论】:

  • 它可能是(或扩展为)一些内置编译器。你用的是什么 C++ 编译器?
  • @BasileStarynkevitch Visual Studio 自带的
  • 不是_ra = reinterpret_cast&lt;__m128&gt;(cd-&gt;data[idx])也可以吗?
  • @Walter 不,您不能在对象上使用reinterpret_cast,即使它们是占位符。见en.cppreference.com/w/cpp/language/reinterpret_cast
  • @plasmacel 正确。但是,可以使用对引用的强制转换。

标签: c++ x86 sse simd intrinsics


【解决方案1】:

SSE 中有显式和隐式加载。

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

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

虽然作为一个扩展,大多数编译器也可以在你做type-punning的时候自动生成XMM加载,但是这样你就无法控制加载是对齐的还是非对齐的。在这种情况下,由于在现代 CPU 上,当数据对齐时使用未对齐加载不会降低性能,因此编译器倾向于普遍使用未对齐加载。

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

不过,我认为显式加载更简洁、更安全。


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

第一个原因在于 SSE 内在函数的设计:在某些情况下您必须使用类型双关语,因为没有其他方法可以使用某些内在函数。 Mysticial's answer总结的很到位。

正如 Cody Gray 在 cmets 中指出的那样,值得一提的是,历史上 MMX 内在函数(现在大多被 SSE2 取代)甚至没有提供显式加载或存储 - 您必须使用类型双关语。

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

GCC 的 typedefs 用于 &lt;xmmintrin.h &gt;&lt;emmintrin.h&gt; 中的 SSE/SSE2 占位符类型:

/* 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 标志启用严格别名时,也可以对这些类型进行类型双关。

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

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

【讨论】:

  • 这个答案的关键部分是严格的别名——显式加载避免了未定义的行为。对于支持英特尔内在函数的编译器不会对 XMM 类型强制执行严格的别名规则这一事实,您是否有某种类型的参考,或者这只是基于您自己的经验?我问是因为它也符合我的经验,但仅仅因为某些东西有效并不意味着它不会冒 UB 风险!
  • 还可以补充一点,这些显式加载对于 SSE 来说是新的。它们不是由 MMX 内在函数提供的,这基本上使得隐式加载和丑陋的强制转换对于 所有 加载操作至关重要。
  • @CodyGray 此行为与 Intel 内部函数没有正式关联,但是有明显的情况是内部函数的设计迫使您使用别名 - 没有其他方法。我推荐这个答案:stackoverflow.com/a/24788226/2430597.
  • 您可以控制负载是否对齐,例如参见非官方的__m128i_u typedef。
  • gcc/clang 在取消引用 __m128i* 时使用对齐加载。如果你要求它,你只会得到不对齐。 (我假设即使 ICC 和 MSVC 仍会将此类负载折叠到 SSE 指令的内存操作数中,例如paddd xmm0, [rsi],这也会给它们一个对齐要求。如果没有 AVX,如果 ICC/MSVC 决定,您可能只会得到未对齐的负载使用单独的加载指令(如movdqu))。
猜你喜欢
  • 1970-01-01
  • 2017-06-08
  • 2012-03-10
  • 2021-01-04
  • 2022-12-05
  • 2011-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多