Эмуляция AVX2 VPSHUFB в AVX

в AVX есть только 128 бит PSHUFB

VPSHUFB xmm1, xmm2, xmm3/m128

и только AVX2 имеет полный PSHUFB для целых 256 бит AVX регистров

VPSHUFB ymm1, ymm2, ymm3/m256

как можно эффективно эмулировать эту инструкцию с помощью встроенных компонентов AVX?

также в этом конкретном случае источник имеет только 8 элементов (байтов), но они могут быть перемещены в пределах полных 32 байтов назначения. Так что это не проблема для запуска только 2 х PSHUFB.

A проблема, которую я нахожу с VPSHUFB он обрабатывает 16 (0x10) как 0, только 128 и выше заполнены нулем! (самый высокий набор битов) можно ли это сделать без добавления сравнений и маскировки?

1 ответов


как заметил @MaratDukhan,_mm256_shuffle_epi8 (т. е. VPSHUFB для ymm-s) не выполняет полную 32-байтовую перетасовку. Что касается меня, то мне очень жаль...

вот почему для того, чтобы эмулировать его без AVX2 вы можете просто разделить каждый регистр на две половины, переставить каждую половину, а затем объединить вместе:

//AVX only
__m256i _emu_mm256_shuffle_epi8(__m256i reg, __m256i shuf) {
    __m128i reg0 = _mm256_castsi256_si128(reg);
    __m128i reg1 = _mm256_extractf128_si256(reg, 1);
    __m128i shuf0 = _mm256_castsi256_si128(shuf);
    __m128i shuf1 = _mm256_extractf128_si256(shuf, 1);
    __m128i res0 = _mm_shuffle_epi8(reg0, shuf0);
    __m128i res1 = _mm_shuffle_epi8(reg1, shuf1);
    __m256i res = _mm256_setr_m128i(res0, res1);
    return res;
}

если вы действительно хотите полностью перетасовать 32-байтовый регистр, вы можете следовать подходу с этой статье. Перемешайте каждую половину с каждой половиной, затем смешайте результаты вместе. Без AVX2 это было бы что-то вроде этого:

//AVX only
__m256i _emu_mm256_shuffle32_epi8(__m256i reg, __m256i shuf) {
    __m128i reg0 = _mm256_castsi256_si128(reg);
    __m128i reg1 = _mm256_extractf128_si256(reg, 1);
    __m128i shuf0 = _mm256_castsi256_si128(shuf);
    __m128i shuf1 = _mm256_extractf128_si256(shuf, 1);
    __m128i res00 = _mm_shuffle_epi8(reg0, shuf0);
    __m128i res01 = _mm_shuffle_epi8(reg0, shuf1);
    __m128i res10 = _mm_shuffle_epi8(reg1, shuf0);
    __m128i res11 = _mm_shuffle_epi8(reg1, shuf1);
    __m128i res0 = _mm_blendv_epi8(res10, res00, _mm_cmplt_epi8(shuf0, _mm_set1_epi8(16)));
    __m128i res1 = _mm_blendv_epi8(res11, res01, _mm_cmplt_epi8(shuf1, _mm_set1_epi8(16)));
    __m256i res = _mm256_setr_m128i(res0, res1);
    return res;
}

если вы точно знаете, что только нижняя половина reg используется, затем вы можете удалить строки для reg1, res10, res11, и удалите сравнение и смешивание. Действительно, было бы более эффективно придерживаться SSE и использовать 128-битные регистры, если у вас нет AVX2.

общая 32-байтовая перетасовка может быть значительно оптимизирована с помощью AVX2:

//Uses AVX2
__m256i _ext_mm256_shuffle32_epi8(__m256i reg, __m256i shuf) {
    __m256i regAll0 = _mm256_permute2x128_si256(reg, reg, 0x00);
    __m256i regAll1 = _mm256_permute2x128_si256(reg, reg, 0x11);
    __m256i resR0 = _mm256_shuffle_epi8(regAll0, shuf);
    __m256i resR1 = _mm256_shuffle_epi8(regAll1, shuf);
    __m256i res = _mm256_blendv_epi8(resR1, resR0, _mm256_cmpgt_epi8(_mm256_set1_epi8(16), shuf));
    return res;
}

будьте осторожны: код не проверен!