如何清除__m256值的高128位?
如何清除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
内在文档说“结果向量的高位未定义”。 同时我可以在assembly中轻松完成:
VMOVDQA xmm2, xmm2 //zeros upper ymm2 VMOVDQA xmm2, xmm1
当然我不想使用“和”或_mm256_insertf128_si256()
等。
不幸的是,理想的解决方案将取决于您使用的是哪种编译器,而在其中一些编译器中,没有理想的解决方案。
我们可以用以下几种基本方法编写:
版本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”中的一种forms完成。)
版本B:编译为效率低下的代码。 习语不被识别,内在函数非常字面地翻译成机器代码指令。 使用VPXOR
将临时YMM寄存器置零,然后使用VPXOR
将其与输入YMM寄存器VPBLENDD
。
版本C:理想。 尽管代码看起来有点可怕且效率低下,但支持AVX2代码生成的所有GCC版本都能识别这种习惯用法。 你得到了预期的VMOVDQA xmm?, xmm?
指令,隐含地清除高位。
喜欢C版!
Clang :
版本A:编译为效率低下的代码。 使用VPXOR
将临时YMM寄存器置零,然后使用VINSERTI128
(或浮点forms,具体取决于版本和选项)将其插入临时YMM寄存器。
版本B和C:也编译为低效的代码。 临时YMM寄存器再次归零,但在这里,它使用VPBLENDD
与输入YMM寄存器VPBLENDD
。
没什么理想的!
ICC :
版本A:理想。 产生预期的VMOVDQA xmm?, xmm?
指令。
版本B:编译为效率低下的代码。 将临时YMM寄存器归零,然后将零与输入YMM寄存器( VPBLENDD
)混合。
版本C:也编译为低效的代码。 将临时YMM寄存器归零,然后使用VINSERTI128
将零值插入临时YMM寄存器。
更喜欢版本A!
MSVC :
版本A和C:编译为低效代码。 将临时YMM寄存器VINSERTI128
,然后使用VINSERTI128
(A)或VINSERTF128
(C)将零值插入临时YMM寄存器。
版本B:也编译为效率低下的代码。 将临时YMM寄存器归零,然后使用VPBLENDD
将其与输入YMM寄存器VPBLENDD
。
没什么理想的!
总之,如果使用正确的代码序列,则可以让GCC和ICC发出理想的VMOVDQA
指令。 但是,我无法看到任何方法让Clang或MSVC安全地发出VMOVDQA
指令。 这些编译器缺少优化机会。
因此,在Clang和MSVC上,我们可以选择XOR + blend和XOR + insert。 哪个更好? 我们转向Agner Fog的指令表 (电子表格版本也可用 ):
在AMD的Ryzen架构上:( Bulldozer-family类似于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指令。 请参阅此AIDA64 InstLatX64结果,以确认VPBLENDW ymm
与Ryzen上的VPBLENDD ymm
执行相同,而不是与VBLENDPS ymm
相同(1个吞吐量来自2个VBLENDPS ymm
,可以在2个端口上运行)。
另请参阅挖掘机/ 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和Intel上都是最佳的,但是我们已经知道了,在它们的代码生成器被改进以识别上述习语之一或者另外一个内在因素之前,它似乎不是Clang或MSVC的选项。添加了这个确切的目的。
幸运的是, VPBLENDD
至少与AMD和Intel CPU上的VINSERTI128
一样出色。 在英特尔处理器上, VPBLENDD
是对VINSERTI128
的重大改进。 (事实上,它几乎和VMOVDQA
一样好,在极少数情况下,后者无法重命名,除了需要一个全零向量常量。)如果你不能哄你的话,更喜欢导致VPBLENDD
指令的内在序列编译器使用VMOVDQA
。
如果你需要一个浮点__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.
也许你可以在此基础上定义自己的基于宏的内在,直到更好的东西降下来。
查看编译器为此生成的内容:
__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。