如何清除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()
之类的词。 最佳答案
更新:现在有一个__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位,这样它们中的任何一个都可以安全地使用。但是哪个是最理想的呢?好吧,那要看你用的是哪个编译器了…
合同一般条款:
版本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架构上:(推土机系列与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
相同(可在2个端口上运行的2个UOP的1C吞吐量)。另请参见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和intel上都是最理想的,但是我们已经知道了,在clang或msvc上,它似乎都不是一个选项,除非它们的代码生成器被改进以识别上述习惯用法中的一个,或者为了这个精确的目的添加了一个额外的内部函数。幸运的是,
VPBLENDD
在amd和intel cpu上至少和VINSERTI128
一样好。在英特尔处理器上,VPBLENDD
比VINSERTI128
有显著改进。(事实上,它几乎和VMOVDQA
一样好,在很少的情况下,后者不能重命名,除了需要全零向量常量。)如果您不能哄编译器使用VPBLENDD
的话,最好选择导致VMOVDQA
指令的内部函数序列。如果需要浮点
__m256
或__m256d
版本,则选择更困难。在Ryzen上,VBLENDPS
的吞吐量为1C,但VINSERTF128
的吞吐量为0.67C。在所有其他CPU(包括AMD推土机系列)上,VBLENDPS
等于或优于。它在intel上要好得多(和integer一样)。如果您是专门为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和con the Godbolt compiler explorer。
也许您可以在此基础上定义自己的基于宏的内在特性,直到有更好的东西出现。
关于c - 如何清除__m256值的高128位?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21384879/