13

如何清除 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

当然,我不喜欢使用“和”或_mm256_insertf128_si256()等。

4

3 回答 3

7

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

m2 = _mm256_zextsi128_si256(m1);

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

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

于 2019-06-14T09:04:59.770 回答
7

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


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

我们可以通过几种基本方式来编写它:

版本 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 位,因此可以安全地使用它们中的任何一个。但哪个是最优化的?好吧,这取决于您使用的是哪个编译器...

海湾合作委员会

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

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

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

更喜欢C版!

铿锵声

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

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

没有什么理想的!

国际商会

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

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

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

更喜欢A版!

MSVC

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

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

没有什么理想的!


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

因此,在 Clang 和 MSVC 上,我们可以在 XOR+blend 和 XOR+insert 之间进行选择。哪个更好?我们转向Agner Fog 的指令表也提供电子表格版本):

在 AMD 的 Ryzen 架构上:(Bulldozer 系列与__m256这些的 AVX 等价物和挖掘机上的 AVX2 类似):

  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 指令。请参阅此 AIDA64 InstLatX64 结果以确认其VPBLENDD ymm性能与VPBLENDW ymmRyzen 上的相同,而不是与VBLENDPS ymm(可在 2 个端口上运行的 2 个微指令的 1c 吞吐量)相同。

另请参阅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它至少与VINSERTI128AMD 和 Intel CPU 一样好。在Intel处理器上VPBLENDD比. (事实上​​,它几乎与后者无法重命名的极少数情况一样好,除了需要一个全零向量常量。)如果你不能哄你的编译器,更喜欢导致指令的内在函数序列使用.VINSERTI128VMOVDQAVPBLENDDVMOVDQA

如果您需要浮点数__m256__m256d它的版本,则选择更加困难。在 Ryzen 上,VBLENDPS吞吐量为 1c,但VINSERTF128为 0.67c。在所有其他 CPU(包括 AMD Bulldozer 系列)上,VBLENDPS性能相同或更好。它在英特尔上要好得多(与整数相同)。如果您专门针对 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

在 Godbolt 编译器资源管理器上分别查看此版本和版本 A、B 和C。

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

于 2017-06-30T14:44:46.707 回答
4

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

__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。

于 2014-01-27T16:30:25.007 回答