如何使用AVX-512实现16位和32位整数插入和提取操作?

时间:2019-10-09 12:16:56

标签: intrinsics avx avx512

AVX具有将16位和32位整数插入和提取到__m256i_mm256_insert_epi16_mm256_insert_epi32_mm256_extract_epi16向量中的指令。

但是,AVX-512似乎没有等效的说明。对_mm256_extract_epi32向量实现这些方法的合适方法是什么?即

  • __m512i
  • __m512i _mm512_insert_epi16(__m512i a, __int16 i, int index)
  • __m512i _mm512_insert_epi32(__m512i a, __int32 i, int index)
  • int _mm512_extract_epi16(__m512i a, int index)

2 个答案:

答案 0 :(得分:4)

相关:

  

AVX具有指令,用于将16位和32位整数插入和提取到__m256i向量中:

不,它不是,_mm256_insert_epi16epi32内在函数是“伪造的”;它们必须由多条指令模拟,_mm_set_epi32(a,b,c,d)并不是任何一条指令都固有的。

IDK为什么英特尔选择为AVX1 / 2而不是AVX512版本提供它们;为什么?也许他们认为,如果他们假设这些代码只花费一次洗牌,就会愚弄人们编写低效率的代码。

vpinsrd ymm_dst, ymm_src, r/m32, imm8(或ZMM)不存在,只有xmm。 (https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq)。 XMM版本无法在__m256i上使用,因为它会将高128位清零。请参阅Using ymm registers as a "memory-like" storage location(您可以使用pinsrd xmm, r/m32, imm的旧版SSE编码插入YMM的低128位,但是在Haswell上这是灾难性的缓慢,因为SSE ​​/ AVX过渡惩罚在那里起作用。但是很好在Skylake或Ryzen上运行。不过,编译器永远不会发出该信号。)

_mm256_insert_epi32可能会与AVX2一起编译以广播负载,而vpblendd可能会从内存中插入dword。或更糟糕的是,对于位于寄存器中的整数,编译器可能会vmovd将其{xmm reg}广播到YMM,然后进行混合。 (就像我在Move an int64_t to the high quadwords of an AVX2 __m256i vector中展示的那样手工完成)


“适当的”实现取决于周围的代码。

如果要插入的元素超过1个,则可能需要在插入之前将它们一起洗牌。甚至考虑矢量存储,多个标量存储,然后重新加载矢量,尽管有存储转发停滞。或者,如果延迟关键路径通过矢量(而不是标量),则标量存储/矢量重新加载以提供混合。如果您有很多小标量元素,则可能值得。


但是,对于单个插入,AVX512F实际上具有一些不错的功能:它具有vpermt2d之类的2输入随机播放,可用于从一个x /的底部插入元素y / zmm移到另一个向量的任何位置(将另一个向量中的所有其余目标元素作为源)。

但此处最有用的是屏蔽广播: uops.info confirms VPBROADCASTW zmm0{k1}, eax是单uup指令,从向量到向量有3个周期的延迟(用于合并),从面具到矢量从eax到合并结果的周期延迟小于等于5。唯一的问题是设置遮罩,但是希望可以将其从循环中吊起,以实现不变的插入位置。

#include <immintrin.h>
#include <stdint.h>
__m512i _mm512_insert32(__m512i target, uint32_t x, const int pos)
{
    return _mm512_mask_set1_epi32(target, 1UL<<pos, x);
}

on Godbolt编译为该asm:

# gcc8.3 -O3 -march=skylake-avx512
_mm512_insert32(long long __vector(8), unsigned int, int):
        mov     eax, 1
        shlx    eax, eax, esi
        kmovw   k1, eax                    # mask = 1<<pos
        vpbroadcastd    zmm0{k1}, edi
        ret

(gcc9无缘无故地浪费了复制ESI的指令)。

使用编译时常数pos,您将得到类似mov eax,2 / kmovw k1, eax的代码;屏蔽广播可能仍然是最佳选择。

这适用于8、16、32或64位元素。对于vpbroadcastb/w窄广播,当然8和16需要AVX512BW,而32和64仅需要AVX512F。


提取:

只需将想要的元素随机排列到__m512i的底部即可使用_mm_cvtsi128_si32。 (在_mm512_castsi512_si128之后)。 valignd很有用,可以通过dword元素进行移位或旋转,使您无需向量控件即可有效地将任何元素移到向量的底部。 https://www.felixcloutier.com/x86/valignd:valignq

答案 1 :(得分:0)

要完成Peter's answer,以下是16和32位插入/提取方法的实现:

#if defined(__GNUC__)

int _mm512_cvtsi512_si32(__m512i a)
{
    __v16si b = (__v16si) a;
    return b[0];
}

#endif

__m512i _mm512_insert_epi16(__m512i target, const std::int16_t x, const int index)
{
    return _mm512_mask_set1_epi16(target, 1UL << index, x);
}
static inline __m512i _mm512_insert_epi32(__m512i target, const std::int32_t x, const int index)
{
    return _mm512_mask_set1_epi32(target, 1UL << index, x);
}

template <int index>
int _mm512_extract_epi32(__m512i target)
{
    return _mm512_cvtsi512_si32(_mm512_alignr_epi32(target, target, index));
}
template <int index>
int  _mm512_extract_epi16(__m512i target)
{
    return (_mm512_extract_epi32<index / 2>(target) >> (index % 2 ? 16 : 0)) & 0xFFFF;
}

请参见example