【问题标题】:Union with __m256 and array of two __m128与 __m256 和两个 __m128 的数组联合
【发布时间】:2012-10-22 09:43:44
【问题描述】:

我可以有这样的工会

  union eight_floats_t
  {
    __m256 a;
    __m128 b[2];
  };
  eight_floats_t eight_floats;

要即时访问 256 位寄存器的两个 128 位部分?

编辑:我想了解这种方法对性能的影响。

【问题讨论】:

  • 你当然可以。但如果编译器不知道如何优化它,就会付出性能损失。

标签: c performance sse vectorization avx


【解决方案1】:

是的,你可以。你试过了吗?

请注意,C 标准规定访问不是最近写入的联合的成员是未指定的行为 - 具体而言,如果您写入一个成员然后读取另一个成员,另一个一个具有未指定的值(C99 §6.2.6.1/7)。然而,它是一个非常常见的习惯用法,并且得到所有主要编译器的良好支持。实际上,以任何顺序读写工会的任何成员都是可接受的做法 (source)。

【讨论】:

  • 你确定这是 UB 吗? gcc 手册实际上推荐了这种做法来避免类型双关指针
  • 我试过了,但我想了解它对性能的影响,正如 Mysticial 所假设的那样。谢谢。
  • @hirschhornsalz:我仔细看了看,你是对的——它不是 UB。 C99 §6.2.6.1/7 说“当一个值存储在联合类型对象的成员中时,对象表示中不对应于该成员但对应于其他成员的字节采用未指定的值。”跨度>
  • @AdamRosenfield 我也仔细看了看,实际上好像没有C99中的UB和C++11中的UB,见stackoverflow.com/questions/11373203/…
【解决方案2】:

你当然可以做到。 C 和 C++ 语言允许您这样做。它很可能会做你想做的事。

但是,您使用 AVX 意味着您关心性能。因此,了解这是 SSE 程序员陷入的最常见(性能)陷阱之一可能会很有用。 (很多人没有注意到)

问题一:

当前的编译器使用内存位置实现这样的联合。所以这是第一个问题,每次您从不同的字段访问联合时,它都会将数据强制到内存并读回。这是一次减速。

以下是 MSVC2010 生成的内容(经过优化):

eight_floats a;
a.a = vecA[0];

__m128 fvecA = a.b[0];
__m128 fvecB = a.b[1];
fvecA = _mm_add_ps(fvecA,fvecB);

vmovaps YMMWORD PTR a$[rbp], ymm0
movaps  xmm1, XMMWORD PTR a$[rbp+16]
addps   xmm1, XMMWORD PTR a$[rbp]
movaps  XMMWORD PTR fvecA$[rbp], xmm1
movss   xmm1, DWORD PTR fvecA$[rbp]

您可以看到它正在被刷新到内存中。

问题 2:

第二次减速甚至更糟。当您将某些内容写入内存并立即以不同的字长访问它时,您可能会触发存储到加载的停顿。 (通常大约 > 10 个周期)

这是因为当前处理器上的加载存储队列通常不是为处理这种(不寻常的)情况而设计的。所以他们通过简单地将队列刷新到内存来处理它。


访问 AVX 数据类型的下半部分和上半部分的“正确”方法是使用:

  • _mm256_extractf128_ps()
  • _mm256_insertf128_ps()
  • _mm256_castps256_ps128()

和家人。其他数据类型也是如此。

也就是说,编译器可能足够聪明,可以识别您在做什么并使用这些指令。 (至少 MSVC2010 没有。)

【讨论】:

  • 值得注意的是,这实际上不应该在当前 µarches 上造成存储转发停滞; 32B 存储被破解为两个 16B 存储微操作,每个微操作都转发到相应的加载操作而不会造成危害。但是,这不应该从您的一般“不要这样做”信息中删除任何内容。
  • 很高兴知道这一点。我不知道英特尔也是如此。虽然我想在未来,32 字节存储可能会成为“原生”。
  • @Mystical:即使它们是原生的,我也希望转发能够继续工作(英特尔实际上已经付出了很大的努力来使转发在所有没有病态错位的情况下都能正常工作——例如,最近的 µarches 将 16B 存储转发到任何不超过 8B 边界的较小负载,以及明显的 16B 负载——顺便说一下,这些都记录在他们的优化手册中。
  • @Mysticial 你在大约 4 年前说过“当前的编译器......”。你的回答还准确吗?
  • @16num I just tested this on GCC6.2 and ICC17. 自 4 年前以来,这种行为根本没有改变。它仍然会在两个方向上通过内存,并会导致存储到加载的停顿。所以不,编译器在这方面并没有变得更好。
猜你喜欢
  • 2012-06-22
  • 1970-01-01
  • 2011-08-21
  • 2011-12-18
  • 1970-01-01
  • 2017-09-01
  • 2018-02-02
  • 2018-04-27
  • 2011-11-26
相关资源
最近更新 更多