英特尔向量指令将以32位int打包的8个4位值零扩展到__m256i?

问题描述 投票:3回答:2

正如问题所说,我有一个普通的int,它是8个打包值,每个4位,我想将其零扩展到256位向量寄存器。这可能与sse / avx / avx2有关吗?

sse avx avx2
2个回答
3
投票

solution by chtz(其余部分称为cvt_nib_epi32_chtz)非常适合一般用途。但是,在某些特定情况下,下面介绍的解决方案可能会稍微提高效率:

/*     gcc -O3 -m64 -Wall -march=skylake cvt_nib_epi32.c     */
#include <immintrin.h>
#include <stdio.h>
#include <stdint.h>

__m256i cvt_nib_epi32_SKL(uint32_t x) {                /* Efficient on Intel Skylake and newer         */
                                                       /* Broadcast x to 8 elements                    */
    __m256i input   = _mm256_set1_epi32(x);            
                                                       /* Shift the nibbles to the right position      */
    __m256i shifted = _mm256_srlv_epi32(input,_mm256_set_epi32(28,24,20,16,12,8,4,0)); 
                                                       /* Mask off the unwanted bits and return        */
            return _mm256_and_si256(shifted, _mm256_set1_epi32(0xF)); 
}


__m256i cvt_nib_epi32_HSW(uint32_t x) {                /* Efficient on intel Haswell and Broadwell     */
                                                       /* Very inefficient in AMD Zen!                 */
    __uint64_t x_b = _pdep_u64(x, 0x0F0F0F0F0F0F0F0F); /* Expand nibbles to bytes                      */
    __m128i    x_v = _mm_cvtsi64_si128(x_b);           /* Move x_b from GPR to AVX vector register     */
    return _mm256_cvtepu8_epi32(x_v);                  /* Convert bytes to integer elements and return */
} 

以下程序集是generated by gcc

cvt_nib_epi32_SKL:
        vmovd   xmm0, edi
        vpbroadcastd    ymm0, xmm0
        vpsrlvd ymm0, ymm0, YMMWORD PTR .LC0[rip]
        vpand   ymm0, ymm0, YMMWORD PTR .LC1[rip]
        ret
cvt_nib_epi32_HSW:
        movabs  rax, 1085102592571150095
        mov     edi, edi
        pdep    rdi, rdi, rax
        vmovq   xmm0, rdi
        vpmovzxbd       ymm0, xmm0
        ret
cvt_nib_epi32_chtz:
        vmovd   xmm0, edi
        vpsrld  xmm1, xmm0, 4
        vpunpcklbw      xmm0, xmm0, xmm1
        vpand   xmm0, xmm0, XMMWORD PTR .LC2[rip]
        vpmovzxbd       ymm0, xmm0
        ret

函数cvt_nib_epi32_chtz非常适合AMD zen微体系结构,因为它不使用pdepvpsrlvd指令,这些指令在这些处理器上很慢。

在Intel处理器上,cvt_nib_epi32_chtz可能受到高端口5(p5)压力的影响,具体取决于周围的代码,因为vmovdvpunpcklbwvpmovzxbd都在p5上执行。其他功能只能解码为2 p5 uop。

Skylake解决方案cvt_nib_epi32_SKL使用vpsrlvd,这在Intel Haswell和Broadwell上很慢。对于这些处理器,cvt_nib_epi32_HSW是合适的。它使用BMI2指令pdep,它在AMD zen微体系结构上非常(!)慢。请注意,cvt_nib_epi32_HSW也应该在Intel Skylake上运行良好,但(再次)实际性能取决于周围的代码。

请注意,在循环上下文中,常量加载(例如YMMWORD PTR .LC0[rip]movabs rax, 1085102592571150095)可能会从循环中提升。在这种情况下,cvt_nib_epi32_HSWcvt_nib_epi32_SKL只需要4个uops。


2
投票

这是一个应该保持顺序的解决方案:

__m256i foo(int x) {
    __m128i input = _mm_cvtsi32_si128(x);
    __m128i even  = input;
    // move odd nibbles to even positions:
    __m128i odd   = _mm_srli_epi32(input,4);
    // interleave: (only lower 64bit are used)
    __m128i inter = _mm_unpacklo_epi8(even, odd);
    // mask out wrong nibbles:
    __m128i masked = _mm_and_si128(inter, _mm_set1_epi32(0x0f0f0f0f));
    // convert to 32bit:
    return _mm256_cvtepu8_epi32(masked);
}

Godbolt链接:https://godbolt.org/z/8RLUVE

如果你一次加载两个或四个int32来进行偶数和奇数半字节的交错和屏蔽,你可以稍微提高效率。 (这当然会导致多个__m256i向量)

© www.soinside.com 2019 - 2024. All rights reserved.