Как скопировать X байтов или битов из __m128i в стандартную память

У меня есть цикл, который добавляет int16 из двух массивов вместе через _mm_add_epi16(). Есть маленький массив и большой массив, результаты записываются обратно в большой массив. Встроенная функция может получить менее 8x int16 (128 бит) из небольшого массива, если он достиг своего конца. Как мне сохранить результаты _mm_add_epi16() обратно в стандартную память int16_t*, если мне не нужны все ее 128 бит? Дополнение массива до степени двойки не вариант. Пример:

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);

Я предполагаю, что мне нужно как-то заменить _mm_store_si128() замаскированным хранилищем.


person GlassBeaver    schedule 30.08.2020    source источник
comment
Вы можете обращаться к его элементам напрямую. Это потому, что __m128i является союзом.   -  person ALX23z    schedule 30.08.2020
comment
Является ли ширина константой времени компиляции на любых путях кода или легко разветвляется? Существуют такие инструкции, как movq и movd, которые могут хранить 8 или 4 байта. Или, если длина smallArray составляет не менее 16, вы можете сделать невыровненный конечный вектор, если вы организуете свой цикл, чтобы оставить результат в переменной, которая будет сохранена на следующей итерации (или при выходе из цикла, после загрузки данных для потенциально перекрывающихся невыровненных конечный вектор).   -  person Peter Cordes    schedule 30.08.2020
comment
@PeterCordes ширина определяется во время выполнения, и мне потребуется 16-битная степень детализации. Возможно, я смогу гарантировать, что размер smallArray будет не менее 16 байтов. Можете ли вы уточнить, что означает невыровненный конечный вектор? Спасибо!   -  person GlassBeaver    schedule 30.08.2020


Ответы (1)


Существует встроенная функция _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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }
};

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

AVX-512 добавляет маскированные хранилища, и вы можете использовать vmovdqu8/vmovdqu16 с соответствующей маской для хранения 8- или 16-битных элементов.

void store_n(__m128i mm, unsigned int n, 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, unsigned int n, 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, &data, 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);
    }
}
person Andrey Semashev    schedule 30.08.2020
comment
Я думаю, что vmaskmovdqu имеет ту же семантику NT; Я думаю, что только AVX vmaskmovps и связанные инструкции с гранулярностью 4 или 8 байт делают простые маскированные хранилища (до AVX512BW vmaskmovdqu8) - person Peter Cordes; 30.08.2020
comment
@PeterCordes Intel SDM документирует невременную подсказку только для maskmovdqu, а не для vmaskmovdqu. У вас есть ссылка, где это также относится к vmaskmovdqu? - person Andrey Semashev; 30.08.2020
comment
@PeterCordes Я нашел описание maskmovdqu/vmaskmovdqu в AMD APM, и описание там действительно предполагает, что обе инструкции имеют невременную подсказку. Я обновил свой ответ. Спасибо. PS: Описание vmaskmovdqu8 мне не удалось найти, поэтому ничего не могу сказать по этому поводу. - person Andrey Semashev; 30.08.2020
comment
Это был бред, AVX512 имеет маскировку для обычных хранилищ, поэтому все векторные mov инструкции имеют размер элемента как часть мнемоники. Я имел в виду vmovdqu8 [rdi]{k1}, xmm0 felixcloutier.com/x86/ - person Peter Cordes; 30.08.2020
comment
Re: Документы Intel: в документах Intel неясно, имеет ли кодировка VEX намек на NT или нет, но я думаю, что их формулировка совместима с реальностью, если мы понимаем, что MASKMOVDQU означает любую кодировку одной и той же инструкции. Я думаю, что в документах Intel нормально говорить только о базовом имени инструкции, когда версии VEX и legacy-SSE работают одинаково. Однако было бы неплохо, если бы документ был более подробным. - person Peter Cordes; 30.08.2020
comment
@AndreySemashev спасибо! Я должен был упомянуть об этом, но у меня есть доступ только к AVX1 и мне нужна 16-битная гранулярность - я думаю, мне придется использовать длинную версию store_n() со всеми этими ifs - person GlassBeaver; 30.08.2020