如何清除__m256值的高128位?

sed*_*eda 13 c x86 simd avx avx2

如何清除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);
Run Code Online (Sandbox Code Playgroud)

不起作用 - 英特尔针对_mm256_castsi128_si256内在函数的文档说"结果向量的高位未定义".同时我可以在装配中轻松完成:

VMOVDQA xmm2, xmm2  //zeros upper ymm2
VMOVDQA xmm2, xmm1
Run Code Online (Sandbox Code Playgroud)

当然我不想用"和"等等_mm256_insertf128_si256().

Cod*_*ray 7

不幸的是,理想的解决方案将取决于你正在使用的编译器,并在其中的一些,还有就是没有理想的解决方案.

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

版本A:

ymm = _mm256_set_m128i(_mm_setzero_si128(), _mm256_castsi256_si128(ymm));
Run Code Online (Sandbox Code Playgroud)

版本B:

ymm = _mm256_blend_epi32(_mm256_setzero_si256(),
                         ymm,
                         _MM_SHUFFLE(0, 0, 3, 3));
Run Code Online (Sandbox Code Playgroud)

版本C:

ymm = _mm256_inserti128_si256(_mm256_setzero_si256(),
                              _mm256_castsi256_si128(ymm),
                              0);
Run Code Online (Sandbox Code Playgroud)

这些中的每一个都正是我们想要的,清除256位YMM寄存器的高128位,因此可以安全地使用它们中的任何一个.但哪个是最优的?那么,这取决于你使用的是哪个编译器......

GCC:

版本A:根本不支持,因为GCC缺乏__m128i _mm256_zextsi128_si256(__m128i)内在性.(当然可以模拟,但可以使用"B"或"C"中的一种形式完成.)

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

版本C:理想.虽然代码看起来有点可怕和低效,但支持AVX2代码生成的所有GCC版本都能识别这个习惯用法.你得到了预期的VPBLENDD指令,它隐含地清除了高位.

更喜欢C版!

Clang:

版本A:编译为效率低下的代码.使用临时YMM寄存器归零VMOVDQA xmm?, xmm?,然后使用VPXOR(或浮点形式,根据版本和选项)将其插入临时YMM寄存器.

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

没什么理想的!

ICC:

版本A:理想.产生预期的VPBLENDD指令.

版本B:编译为效率低下的代码.将临时YMM寄存器归零,然后将零与输入YMM寄存器(VMOVDQA xmm?, xmm?)混合.

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

更喜欢版本A!

MSVC:

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

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

没什么理想的!


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

因此,在Clang和MSVC上,我们可以选择XOR + blend和XOR + insert.哪个更好?我们转向Agner Fog的指令表(电子表格版本也可用):

在AMD的Ryzen架构上:( Bulldozer-family类似于AVX VMOVDQA等价物,以及针对挖掘机上的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
Run Code Online (Sandbox Code Playgroud)

Agner Fog似乎错过了他桌子Ryzen部分的一些AVX2指令.请参阅此AIDA64 InstLatX64结果__m256进行与VPBLENDD ymmRyzen 相同的确认,而不是相同VPBLENDW ymm(1个吞吐量来自2个uop,可以在2个端口上运行).

另请参阅挖掘机/ Carrizo InstLatX64,其中显示VBLENDPS ymmVPBLENDD具有相同的性能(2个循环延迟,每个循环吞吐量1个).同样的VINSERTI128/ VBLENDPS.

在英特尔架构上(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
Run Code Online (Sandbox Code Playgroud)

显然,VINSERTF128AMD和Intel都是最优的,但是我们已经知道了,在它们的代码生成器被改进以识别上述习语之一或添加了额外的内在函数之前,它似乎不是Clang或MSVC的选项为了这个目的.

幸运的VMOVDQA是,至少与VPBLENDDAMD和Intel CPU 一样好.在英特尔处理器上,VINSERTI128是一项重大改进VPBLENDD.(事实上​​,它几乎VINSERTI128与罕见的情况一样好,后者无法重命名,除了需要一个全零向量常量.)VMOVDQA如果你不能哄你的编译器,那么选择导致指令的内在函数序列.用VPBLENDD.

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

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

也许你可以在此基础上定义你自己的基于宏的内在,直到更好的东西降下来.


A F*_*Fog 5

已添加新的内部函数来解决此问题:

m2 = _mm256_zextsi128_si256(m1);
Run Code Online (Sandbox Code Playgroud)

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

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


Pau*_*l R 4

查看编译器为此生成的内容:

__m128i m1 = _mm_set1_epi32(1);
__m256i m2 = _mm256_set_m128i(_mm_setzero_si128(), m1);
Run Code Online (Sandbox Code Playgroud)

或者这样:

__m128i m1 = _mm_set1_epi32(1);
__m256i m2 = _mm256_setzero_si256();
m2 = _mm256_inserti128_si256 (m2, m1, 0);
Run Code Online (Sandbox Code Playgroud)

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