如何清除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()
等。
答案 0 :(得分:5)
不幸的是,理想的解决方案取决于您使用的是哪种编译器,而其中一些编译器 并不是理想的解决方案。
我们可以用几种基本方法来写这个:
版本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
内在函数。 (当然可以模拟,但这可以使用&#34; B&#34;或&#34; C&#34;中的一种形式完成。)
版本B:编译为效率低下的代码。习语不被识别,内在因素被非常字面地翻译成机器代码指令。使用VPXOR
将临时YMM寄存器归零,然后使用VPBLENDD
将其与输入YMM寄存器混合。
版本C:理想。虽然代码看起来有点可怕和低效,但支持AVX2代码生成的所有GCC版本都能识别这个习惯用法。您将获得预期的VMOVDQA xmm?, xmm?
指令,该指令隐式清除高位。
首选版本C!
<强>锵强>:
版本A:编译为效率低下的代码。临时YMM寄存器使用VPXOR
归零,然后使用VINSERTI128
(或浮点形式,根据版本和选项)将其插入临时YMM寄存器。
版本B&amp; C:还编译成效率低下的代码。临时YMM寄存器再次归零,但在这里,它使用VPBLENDD
与输入YMM寄存器混合。
没什么理想的!
<强> ICC 强>:
版本A:理想。生成预期的VMOVDQA xmm?, xmm?
指令。
版本B:编译为效率低下的代码。将临时YMM寄存器归零,然后将零与输入YMM寄存器(VPBLENDD
)混合。
版本C:也编译为效率低下的代码。将临时YMM寄存器归零,然后使用VINSERTI128
将0插入临时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架构:(推土机系列与AVX __m256
相当于这些,以及挖掘机上的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指令。请参阅this AIDA64 InstLatX64 result以确认VPBLENDD ymm
与Ryzen上的VPBLENDW ymm
执行相同,而不是与VBLENDPS ymm
相同(1个吞吐量来自2个uop,可在2个端口上运行)
另请参阅an Excavator / Carrizo InstLatX64,表明VPBLENDD
和VINSERTI128
在那里具有相同的效果(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和英特尔上都是最佳的,但我们已经知道,在它们的代码生成器被改进以识别一个之前,它似乎不是Clang或MSVC的选项为了这个确切的目的,增加了上述习语或附加内在词。
幸运的是,VPBLENDD
在AMD和Intel CPU上至少与VINSERTI128
一样好。在英特尔处理器上,VPBLENDD
比VINSERTI128
提高了重要。 (实际上,在极少数情况下它不能重命名,它几乎与VMOVDQA
一样好,除非需要一个全零向量常量。)更喜欢导致{的内在函数序列{1}}如果您不能哄骗编译器使用VPBLENDD
。
如果您需要浮点VMOVDQA
或__m256
版本,则选择更加困难。在Ryzen上,__m256d
具有1c吞吐量,但VBLENDPS
具有0.67c。在所有其他CPU(包括AMD Bulldozer系列)上,VINSERTF128
相等或更好。它在英特尔上更好更好(与整数相同)。如果您专门针对AMD进行了优化,则可能需要进行更多测试,以查看特定代码序列中哪个变体最快,否则混合。 Ryzen只会稍微糟糕一点。
总之,然后,针对通用x86并支持尽可能多的不同编译器,我们可以这样做:
VBLENDPS
分别查看此内容以及版本A,B和C on the Godbolt compiler explorer。
也许你可以在此基础上定义自己的基于宏的内在,直到更好的东西降下来。
答案 1 :(得分:5)
已添加新的内部函数来解决此问题:
m2 = _mm256_zextsi128_si256(m1);
如果已知上半部分为零,则此函数不会产生任何代码,只是确保不会将上半部分视为未定义。
答案 2 :(得分:2)
查看编译器为此生成的内容:
__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。