_mm_alignr_epi8 (PALIGNR) эквивалент в AVX2

В SSE3 команда PALIGNR выполняет следующее:

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

В настоящее время я занимаюсь переносом моего кода SSE4 для использования инструкций AVX2 и работы с 256-битными регистрами вместо 128 бит. Наивно, я полагал, что функция intrinsics _mm256_alignr_epi8 (VPALIGNR) выполняет ту же операцию, что и _mm_alignr_epi8 только на 256-битных регистрах. К сожалению, это не совсем так. Фактически, _mm256_alignr_epi8 рассматривает 256-битный регистр как 2 128-битные регистры и выполняет 2 операции "выравнивания" в двух соседних 128-битных регистрах. Эффективно выполняет ту же операцию, что и _mm_alignr_epi8, но на 2 регистрах одновременно. Это наиболее ярко проиллюстрировано здесь: _ mm256_alignr_epi8

В настоящее время я решил сохранить _mm_alignr_epi8, разделив регистры ymm (256 бит) на два xmm (128 бит) регистра (высокий и низкий), например:

__m128i xmm_ymm1_hi = _mm256_extractf128_si256(ymm1, 0);
__m128i xmm_ymm1_lo = _mm256_extractf128_si256(ymm1, 1);
__m128i xmm_ymm2_hi = _mm256_extractf128_si256(ymm2, 0);
__m128i xmm_ymm_aligned_lo = _mm_alignr_epi8(xmm_ymm1_lo, xmm_ymm1_hi, 1);
__m128i xmm_ymm_aligned_hi = _mm_alignr_epi8(xmm_ymm2_hi, xmm_ymm1_lo, 1);
__m256i xmm_ymm_aligned = _mm256_set_m128i(xmm_ymm_aligned_lo, xmm_ymm_aligned_hi);

Это работает, но должен быть лучший способ, не так ли? Есть ли, возможно, более "общая" инструкция AVX2, которая должна использоваться для получения того же результата?

Ответ 1

Для чего вы используете palignr для? Если это нужно только для обработки несогласованности данных, вместо этого используйте вместо этого неправильно настроенные нагрузки; они, как правило, "достаточно быстро" на современных Intel-архитектурах (и сэкономит вам много размера кода).

Если вам нужно поведение palignr по какой-либо другой причине, вы можете просто воспользоваться поддержкой без выравнивания, чтобы сделать это без ведома. Если вы не полностью привязаны к загрузке, это, вероятно, предпочтительная идиома.

static inline __m256i _mm256_alignr_epi8(const __m256i v0, const __m256i v1, const int n)
{
    // Do whatever your compiler needs to make this buffer 64-byte aligned.
    // You want to avoid the possibility of a page-boundary crossing load.
    char buffer[64];

    // Two aligned stores to fill the buffer.
    _mm256_store_si256((__m256i *)&buffer[0], v0);
    _mm256_store_si256((__m256i *)&buffer[32], v1);

    // Misaligned load to get the data we want.
    return _mm256_loadu_si256((__m256i *)&buffer[n]);
}

Если вы можете предоставить дополнительную информацию о том, как именно вы используете palignr, я, вероятно, могу быть более полезным.

Ответ 2

Единственное решение, с которым я смог справиться, это:

static inline __m256i _mm256_alignr_epi8(const __m256i v0, const __m256i v1, const int n)
{
  if (n < 16)
  {
    __m128i v0h = _mm256_extractf128_si256(v0, 0);
    __m128i v0l = _mm256_extractf128_si256(v0, 1);
    __m128i v1h = _mm256_extractf128_si256(v1, 0);
    __m128i vouth = _mm_alignr_epi8(v0l, v0h, n);
    __m128i voutl = _mm_alignr_epi8(v1h, v0l, n);
    __m256i vout = _mm256_set_m128i(voutl, vouth);
    return vout;
  }
  else
  {
    __m128i v0h = _mm256_extractf128_si256(v0, 1);
    __m128i v0l = _mm256_extractf128_si256(v1, 0);
    __m128i v1h = _mm256_extractf128_si256(v1, 1);
    __m128i vouth = _mm_alignr_epi8(v0l, v0h, n - 16);
    __m128i voutl = _mm_alignr_epi8(v1h, v0l, n - 16);
    __m256i vout = _mm256_set_m128i(voutl, vouth);
    return vout;
  }
}

который, как я думаю, в значительной степени идентичен вашему решению, за исключением того, что он также обрабатывает сдвиги >= 16 байт.