如何从__m128i复制X字节或位到标准内存

问题描述

我有一个循环,它通过_mm_add_epi16()将两个数组中的int16加在一起。有一个小数组和一个大数组,结果被写回到大数组中。 如果内在函数到达末尾,则内在函数可能会从小型数组中获取少于8x int16s(128位)的结果-当我不希望将_mm_add_epi16()的结果都存储到标准内存int16_t *中时,如何将其存储到标准存储器int16_t *中位?不能将数组填充为2的幂。示例:

int16_t* smallArray;
int16_t* largeArray;
__m128i inSmallArray = _mm_load_si128((__m128i*)smallArray);
__m128i* pInLargeArray = (__m128i*)largeArray;
__m128i inLargeArray = _mm_load_si128(pInLargeArray);
inLargeArray = _mm_add_epi16(inLargeArray,inSmallArray);
_mm_store_si128(pInLargeArray,inLargeArray);

我的猜测是我需要以某种方式将“ {mask}”替换为_mm_store_si128()

解决方法

有一个_mm_maskmoveu_si128内部函数,它转换为maskmovdqu(在SSE中)或vmaskmovdqu(在AVX中)。

// Store masks. The highest bit in each byte indicates the byte to store.
alignas(16) const unsigned char masks[16][16] =
{
    { 0x00,0x00,0x00 },{ 0xFF,0xFF,0x00 }
};

void store_n(__m128i mm,unsigned int n,void* storage)
{
    assert(n < 16u);
    _mm_maskmoveu_si128(mm,reinterpret_cast< const __m128i& >(masks[n]),static_cast< char* >(storage));
}

此代码的问题在于maskmovdqu(并且可能是vmaskmovdqu)指令具有非临时访问目标内存的关联提示,这使指令昂贵,并且还需要一个然后围起来。

AVX添加了新的指令vmaskmovps / vmaskmovpd(而AVX2也添加了vpmaskmovd / vpmaskmovq),其工作原理与vmaskmovdqu类似,但没有非时间提示,并且只能在32位和64位粒度上运行。

// Store masks. The highest bit in each 32-bit element indicates the element to store.
alignas(16) const unsigned char masks[4][16] =
{
    { 0x00,void* storage)
{
    assert(n < 4u);
    _mm_maskstore_epi32(static_cast< int* >(storage),mm);
}

AVX-512添加了掩码存储,您可以将vmovdqu8 / vmovdqu16与适当的掩码一起使用来存储8位或16位元素。

void store_n(__m128i mm,void* storage)
{
    assert(n < 16u);
    _mm_mask_storeu_epi8(storage,static_cast< __mmask16 >((1u << n) - 1u),mm);
}

请注意,以上内容需要AVX-512BW和VL扩展名。

如果您需要8位或16位粒度并且没有AVX-512,那么最好使用逐段手动存储向量寄存器的功能。

void store_n(__m128i mm,void* storage)
{
    assert(n < 16u);

    unsigned char* p = static_cast< unsigned char* >(storage);
    if (n >= 8u)
    {
        _mm_storel_epi64(reinterpret_cast< __m128i* >(p),mm);
        mm = _mm_unpackhi_epi64(mm,mm); // move high 8 bytes to the low 8 bytes
        n -= 8u;
        p += 8;
    }

    if (n >= 4u)
    {
        std::uint32_t data = _mm_cvtsi128_si32(mm);
        std::memcpy(p,&data,sizeof(data)); // typically generates movd
        mm = _mm_srli_si128(mm,4);
        n -= 4u;
        p += 4;
    }

    if (n >= 2u)
    {
        std::uint16_t data = _mm_extract_epi16(mm,0); // or _mm_cvtsi128_si32
        std::memcpy(p,sizeof(data));
        mm = _mm_srli_si128(mm,2);
        n -= 2u;
        p += 2;
    }

    if (n > 0u)
    {
        std::uint32_t data = _mm_cvtsi128_si32(mm);
        *p = static_cast< std::uint8_t >(data);
    }
}