【问题标题】:How to clear the upper 128 bits of __m256 value?如何清除__m256值的高128位?
【发布时间】:2014-02-18 12:41:31
【问题描述】:

如何清除m2的高128位:

__m256i    m2 = _mm256_set1_epi32(2);
__m128i    m1 = _mm_set1_epi32(1);

m2 = _mm256_castsi128_si256(_mm256_castsi256_si128(m2));
m2 = _mm256_castsi128_si256(m1);

不起作用——英特尔的 _mm256_castsi128_si256 内在文档说“结果向量的高位未定义”。 同时我可以很容易地在汇编中做到这一点:

VMOVDQA xmm2, xmm2  //zeros upper ymm2
VMOVDQA xmm2, xmm1

我当然不想用“and”或_mm256_insertf128_si256()之类的。

【问题讨论】:

  • 使用内联汇编有什么问题?如果您正在使用 AVX 内部函数,则您已经是特定于处理器的。
  • Sergey:64 位 VC 中没有内联汇编。除此之外,C 编译器通常会创建比我更快的代码——它可以使用智能指令顺序和其他技巧。
  • _mm256_zeroupper。好的,它会做的比你想要的多一点;-)
  • 使用 gcc,__m256i y={x[0],x[1],0,0}; 生成单个 vmovdqa
  • @SergeyL.:在你希望编译器优化的东西中间,内联汇编有很多问题。 gcc.gnu.org/wiki/DontUseInlineAsm 指出它会破坏持续传播等。

标签: c x86 simd avx avx2


【解决方案1】:

添加了一个新的内在函数来解决这个问题:

m2 = _mm256_zextsi128_si256(m1);

https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm256_zextsi128_si256&expand=6177,6177

如果已知上半部分为零,则此函数不会生成任何代码,它只是确保上半部分不被视为未定义。

【讨论】:

  • 这适用于 Clang 和 MS 编译器,但不适用于 Gcc。
  • Fixed on GCC trunk。猜猜我们会在 GCC 10 中看到它们。
【解决方案2】:

更新:现在有一个 __m128i _mm256_zextsi128_si256(__m128i) 内在函数;见Agner Fog's answer。下面的其余答案仅与不支持此内在函数的旧编译器相关,并且没有高效、可移植的解决方案。


不幸的是,理想的解决方案将取决于您使用的编译器,而对于其中一些编译器,没有理想的解决方案。

我们可以用几种基本的方式来写这个:

A 版

ymm = _mm256_set_m128i(_mm_setzero_si128(), _mm256_castsi256_si128(ymm));

B 版

ymm = _mm256_blend_epi32(_mm256_setzero_si256(),
                         ymm,
                         _MM_SHUFFLE(0, 0, 3, 3));

C 版

ymm = _mm256_inserti128_si256(_mm256_setzero_si256(),
                              _mm256_castsi256_si128(ymm),
                              0);

它们中的每一个都精确地执行我们想要的操作,清除 256 位 YMM 寄存器的高 128 位,因此可以安全地使用它们中的任何一个。但哪个是最优化的?好吧,这取决于您使用的是哪个编译器...

GCC

版本 A:根本不支持,因为 GCC 缺少 _mm256_set_m128i 内在函数。 (当然可以模拟,但可以使用“B”或“C”中的一种形式来完成。)

版本 B:编译为低效代码。成语不被识别,内在函数被非常字面地翻译成机器代码指令。使用 VPXOR 将临时 YMM 寄存器归零,然后使用 VPBLENDD 将其与输入 YMM 寄存器混合。

版本 C:理想。尽管代码看起来有点吓人且效率低下,但所有支持 AVX2 代码生成的 GCC 版本都可以识别这个习语。你得到了预期的VMOVDQA xmm?, xmm? 指令,它隐式地清除了高位。

更喜欢 C 版!

叮当

版本 A:编译为低效代码。使用 VPXOR 将临时 YMM 寄存器归零,然后使用 VINSERTI128(或浮点形式,取决于版本和选项)将其插入到临时 YMM 寄存器中。

版本 B 和 C:也编译为低效代码。临时 YMM 寄存器再次清零,但在这里,它使用 VPBLENDD 与输入 YMM 寄存器混合。

没有什么理想的!

国际商会

版本 A:理想。产生预期的VMOVDQA xmm?, xmm? 指令。

版本 B:编译为低效代码。将临时 YMM 寄存器归零,然后将零与输入 YMM 寄存器 (VPBLENDD) 混合。

版本 C:也编译为低效代码。将临时 YMM 寄存器归零,然后使用 VINSERTI128 将零插入临时 YMM 寄存器。

更喜欢 A 版!

MSVC

版本 A 和 C:编译为低效代码。将临时 YMM 寄存器清零,然后使用 VINSERTI128 (A) 或 VINSERTF128 (C) 将零插入临时 YMM 寄存器。

版本 B:也编译为低效代码。将临时 YMM 寄存器归零,然后使用 VPBLENDD 将其与输入 YMM 寄存器混合。

没有什么理想的!


总之,如果使用正确的代码序列,GCC 和 ICC 可以发出理想的VMOVDQA 指令。但是,我看不到任何方法可以让 Clang 或 MSVC 安全地发出 VMOVDQA 指令。这些编译器错过了优化机会。

因此,在 Clang 和 MSVC 上,我们可以在 XOR+blend 和 XOR+insert 之间进行选择。哪个更好?我们转向Agner Fog's instruction tables(电子表格版本also available):

在 AMD 的 Ryzen 架构上:(Bulldozer 系列与 AVX __m256 等价物类似,对于 AVX2 on Excavator):

  Instruction   | Ops | Latency | Reciprocal Throughput |   Execution Ports
 ---------------|-----|---------|-----------------------|---------------------
   VMOVDQA      |  1  |    0    |          0.25         |   0 (renamed)
   VPBLENDD     |  2  |    1    |          0.67         |   3
   VINSERTI128  |  2  |    1    |          0.67         |   3

Agner Fog 似乎遗漏了他表格 Ryzen 部分中的一些 AVX2 指令。请参阅 this AIDA64 InstLatX64 result 以确认 VPBLENDD ymm 在 Ryzen 上的性能与 VPBLENDW ymm 相同,而不是与 VBLENDPS ymm 相同(1c 吞吐量来自可以在 2 个端口上运行的 2 微指令)。

另请参见 an Excavator / Carrizo InstLatX64,这表明 VPBLENDDVINSERTI128 在那里具有相同的性能(2 个周期延迟,每个周期 1 个吞吐量)。 VBLENDPS/VINSERTF128 也一样。

关于英特尔架构(Haswell、Broadwell 和 Skylake):

  Instruction   | Ops | Latency | Reciprocal Throughput |   Execution Ports
 ---------------|-----|---------|-----------------------|---------------------
   VMOVDQA      |  1  |   0-1   |          0.33         |   3 (may be renamed)
   VPBLENDD     |  1  |    1    |          0.33         |   3
   VINSERTI128  |  1  |    3    |          1.00         |   1

显然,VMOVDQA 在 AMD 和 Intel 上都是最佳选择,但我们已经知道这一点,而且在 Clang 或 MSVC 的代码生成器经过改进以识别上述习惯用法之前,它似乎不是一个选项或者为了这个精确的目的添加了一个额外的内在函数。

幸运的是,VPBLENDD 在 AMD 和 Intel CPU 上至少与VINSERTI128 一样好。在 Intel 处理器上,VPBLENDD 是对VINSERTI128显着改进。 (事实上​​,它几乎和VMOVDQA 一样好,在后者无法重命名的极少数情况下,除了需要一个全零向量常量。)如果可以的话,更喜欢导致VPBLENDD 指令的内在函数序列不要哄你的编译器使用VMOVDQA

如果你需要一个浮点的__m256__m256d 这个版本,选择就比较困难了。在 Ryzen 上,VBLENDPS 的吞吐量为 1c,但VINSERTF128 的吞吐量为 0.67c。在所有其他 CPU(包括 AMD Bulldozer 系列)上,VBLENDPS 等于或更好。它在 Intel 上更好(与整数相同)。如果您专门针对 AMD 进行优化,您可能需要进行更多测试以查看在您的特定代码序列中哪个变体最快,否则混合。在 Ryzen 上只差一点点。

总之,那么,针对通用 x86 并支持尽可能多的不同编译器,我们可以做到:

#if (defined _MSC_VER)

    ymm = _mm256_blend_epi32(_mm256_setzero_si256(),
                             ymm,
                             _MM_SHUFFLE(0, 0, 3, 3));

#elif (defined __INTEL_COMPILER)

    ymm = _mm256_set_m128i(_mm_setzero_si128(), _mm256_castsi256_si128(ymm));

#elif (defined __GNUC__)

    // Intended to cover GCC and Clang.
    ymm = _mm256_inserti128_si256(_mm256_setzero_si256(),
                                  _mm256_castsi256_si128(ymm),
                                  0);

#else
    #error "Unsupported compiler: need to figure out optimal sequence for this compiler."
#endif

请分别查看此版本和版本 A、B 和 C on the Godbolt compiler explorer

也许您可以在此基础上定义自己的基于宏的内在函数,直到出现更好的情况。

【讨论】:

  • 我还尝试在 ymm 的上部通道中插入一个零通道:_mm256_inserti128_si256(ymm, _mm_setzero_si128(), 1);。 gcc 将其编译为实际的vinserti128,然后 clang 将其转换为混合,因此没有什么新东西。 ICC 将其编译为VMOVDQA
  • 相关:英特尔 CPU 永远不会消除 vmovdqa same,samemov same,same。当寄存器不同时,它们几乎总是成功的,除非你有一个重命名链,中间没有 ALU 的东西。 (例如 movdqa xmm0, xmm1 / movdqa xmm1, xmm0 在循环中)。那么有的会在重命名的时候处理,有的会占用一个执行单元。
  • 如果我不使用/arch:AVX,MSVC 在版本A 中使用非AVX xorps xmm2,xmm2!!! godbolt.org/g/UwSvWh
  • 感谢您的编辑,@Peter!看到 Fog 报告 AMD 的混合速度如此之快,我有点惊讶,但我没想到在其他地方进行验证。我没有这些 CPU。我什至没有见过 Ryzen。你说他“错过了 Ryzen 部分的一些 AVX2 说明”,但说明在那里,只是数字不正确。至于 MSVC,我并不感到惊讶。我什至不认为它会被认为是一个错误。如果您使用的是 AVX 内在函数,您确实需要告诉编译器以 AVX 为目标。混合模式的二进制文件是行不通的。
  • 感谢您维护这个答案,@Peter。我非常感谢您为保持 Stack Overfow 的准确性、最新性和信息量所做的所有工作。
【解决方案3】:

看看你的编译器为此生成了什么:

__m128i m1 = _mm_set1_epi32(1);
__m256i m2 = _mm256_set_m128i(_mm_setzero_si128(), m1);

或者这个:

__m128i m1 = _mm_set1_epi32(1);
__m256i m2 = _mm256_setzero_si256();
m2 = _mm256_inserti128_si256 (m2, m1, 0);

我这里的 clang 版本似乎为任何一个 (vxorps + vinsertf128) 生成相同的代码,但 YMMV。

【讨论】:

  • Paul:我所有的编译器(ICC 14、VC 17、GC 4.8.1)都使用 vinserti128。使用 m2 = _mm256_castsi128_si256(m1) 他们都使用更快的 vmovdqa 并清除上半部分,但我不确定我是否可以依赖它。
猜你喜欢
  • 2020-07-04
  • 2011-12-09
  • 2018-03-12
  • 1970-01-01
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-27
相关资源
最近更新 更多