【问题标题】:Unaligned vector pointers oddities (AVX512)未对齐的向量指针异常 (AVX512)
【发布时间】:2020-09-22 09:23:59
【问题描述】:

我的问题涉及派生未对齐的 __m512 指向包含浮点数的内存空间的指针。我发现在通过这样的装置访问内存时,GCC 和 Clang 在生成正确的 uop(未对齐与对齐)方面有些不稳定。

首先,工作案例:

typedef float MyFloatVector __attribute__((vector_size(64), aligned(4)));
MyFloatVector* vec_ptr = reinterpret_cast<MyFloatVector*>(float_ptr);
Something(*vec_ptr);

Clang 和 GCC 都为上述内容生成 MOVUPS。但是,如果 vec_ptr 的类型留给编译器:

typedef float MyFloatVector __attribute__((vector_size(64), aligned(4)));
auto vec_ptr = reinterpret_cast<MyFloatVector *>(float_ptr);
Something(*vec_ptr);

现在,Clang 将生成 MOVAPS 和一个段错误。 GCC 仍然会生成 MOVUPS,但也会生成三个无操作指令(push rbp、将 rsp 加载到 rbp、pop rbp)。

另外,如果我从 typedef 更改为 using:

using MyFloatVector = float __attribute__((vector_size(64), aligned(4)));
MyFloatVector*vec_ptr = reinterpret_cast<MyFloatVector*>(float_ptr);
Something(*vec_ptr);

GCC 再次生成绒毛指令,Clang 生成 MOVAPS。在此处使用 auto 会产生相同的结果。

那么,有没有人知道幕后发生了什么,是否有一种安全的方法来进行转换。虽然存在一个可行的解决方案,但 IMO 由 typedef/using 和显式/自动生成的差异使得它太不可靠而无法放心使用——至少我需要一个静态断言来检查在取消引用指针时生成的 uop未对齐,不存在 AFAIK。

在某些情况下,我可能希望对内存区域有一个 MyFloatVector 引用,这排除了使用内在函数。

示例代码:https://godbolt.org/z/caxScz。包括“乐趣”的 ICC,它会在整个过程中生成 MOVUPS。

【问题讨论】:

  • reinterpret_cast 经常使用不安全。 _mm512_loadu_ps 有什么问题?
  • 它排除了使用引用,例如MyFloatVector& vec_ref = *vec_ptr;目标是实现一个类似 std::vector 的容器,该容器可以对矢量化数据类型进行操作,因此例如[]-operator 需要能够形成对内部存储的引用。
  • 也许实现一个自定义引用对象(它内部包含一个指针,但重载了operator __m512()operator=(__m512)——类似于vector&lt;bool&gt; 中处理引用的方式。
  • 是的——这似乎是唯一的出路。我有点希望在这里变得懒惰,只依靠编译器来做魔术:-) 不过谢谢你的帮助!

标签: gcc clang vectorization simd avx512


【解决方案1】:

当您使用reinterpret_cast 时,您是在告诉编译器该参数指向所请求类型的有效对象。这意味着它具有相同的对齐要求。

ICC 在这里更加保守,而 clang 和 GCC 试图通过假设您实际上遵守标准来使您的代码运行得更快。

请记住,aligned 属性只能用于增加对齐要求,而不是减少对齐要求,因此在您的代码中,您只是说类型具有 minimum 对齐 4 个字节。如果您添加 static_assert(alignof(MyFloatVector) == 4, "Alignment should be 4"),您可能会看到一些失败,具体取决于您声明它的方式。

由于您没有使用__m512_mm512_loadu_ps 可以工作,但可能不是真的 恕我直言。加载未对齐数据的正确方法是使用memcpy(或__builtin_memcpy,因为无论如何您都在使用矢量扩展)。编译器非常擅长优化已知大小的 memcpy,只要您使用的是相对较新的编译器,您最终应该会在启用 AVX-512F 的 x86 上使用 vmovups。

【讨论】:

  • vmovdqu 在恰好对齐的地址上与vmovdqa 一样快。 GCC 和 clang 正在做的是帮助您验证您承诺的对齐方式,前提是您故意这样做并且想要在未对齐时出错。
  • 另外,有趣的是,GCC/clang 标头实现了 loadu / storeu 内部函数和 typedef float __attribute__ ((vector_size(64), aligned(1), may_alias)) __m512_u;__attribute__ 确实可以让您告诉 GCC 未对齐类型(与 ISO C alignas 不同),并且也适用于未对齐的标量类型。另见Is `reinterpret_cast`ing between hardware SIMD vector pointer and the corresponding type an undefined behavior?。但是你应该只使用_mm512_loadu_ps 而不是自己重新实现它或乱用 GNU C 原生向量的东西。
  • 感谢您的回答!我读过关于aligned() 只能增加对齐的文章——忽略它确实会改变 GCC 和 Clang 的行为(然后在所有情况下都生成对齐的负载),所以这让我很困惑初始对齐是否是在示例情况下假定来自floatfloat __attribute__ ((vector_size(64)))。我错过了reinterpret_cast 的假设,它确实有道理。为什么typedef 类型的行为不同于using 以及autovs 显式类型的行为对我来说仍然很奇怪。
猜你喜欢
  • 2020-10-23
  • 1970-01-01
  • 2015-11-10
  • 2014-02-23
  • 2013-01-04
  • 1970-01-01
  • 1970-01-01
  • 2015-07-13
  • 2020-03-25
相关资源
最近更新 更多