Извлеките и сохраните альтернативные младшие 32 бита из регистра AVX

#vectorization #simd #intrinsics #avx2

#векторизация #simd #встроенные #avx2

Вопрос:

У меня есть регистр __m256i, и я хочу извлечь 4 младших 32 бита из каждой 64-битной группы, упаковать их и сохранить непрерывно в памяти. Т.е., если регистр __m256i содержит 8 32-битных слов: { a0, a1, a2, a3, a4, a5, a6, a7 }, я хочу сохранить в памяти последовательно четыре слова { a0, a2, a4, a6 }

Я придумал следующий код:

 void mystore(uint32 *dst, const __m256iamp; src)
{
      __m256 ps256    = _mm256_castsi256_ps(src);
      __m128 lo128    = _mm256_extractf128_ps(ps256, 0);
      __m128 hi128    = _mm256_extractf128_ps(ps256, 1);
      __m128 pack128  = _mm_shuffle_ps(lo128, hi128, 0   (2<<2)   (0<<4)   (2<<6));
      __m128i r    = _mm_castps_si128(pack);
     _mm256_storeu_si256( reinterpret_cast<__m128i*>(dst), r )
}
  

Если я прав, операции приведения используются только для проверки типов компилятора, но фактически они эквивалентны безоперационному режиму, поэтому общая стоимость задержки составляет 3 для инструкции перемешивания и 2 инструкций извлечения, плюс стоимость невыровненного хранилища.

Есть ли более быстрый способ сделать это?

Спасибо

Комментарии:

1. _mm256_extractf128_ps(ps256, 0); это также просто приведение. Нижняя половина каждого регистра YMM доступна как соответствующий регистр XMM, и, к счастью, компиляторы знают это и не наказывают нас за запись extract(v, 0) вместо того, что более прямо выражает правильная _mm_cast внутренняя функция оптимального asm. (Аналогично, компиляторы используют MOVD при записи _mm_extract_epi32(v, 0) вместо фактического PEXTRD).

2. Интересно, я этого не знал.

Ответ №1:

Вы можете попробовать использовать что-то вроде этого:

 const __m256i K_PERM = _mm256_setr_epi32(0, 2, 4, 6, 1, 3, 5, 7);

inline void mystore(uint32_t * dst, const __m256i amp; src)
{
    __m256i permuted = _mm256_permutevar8x32_epi32(src, K_PERM);
    __m128i lo128 = _mm256_extractf128_si256(permuted, 0);
    _mm_storeu_si128((__m128i*)dst, lo128);
}
  

Комментарии:

1. Учитывая, что команда перестановки имеет задержку 3, исходя из приведенных выше комментариев, это кажется более дорогостоящим, чем существующее решение. Тем не менее, я попробую.

2. Я думаю, что основным ограничением является пропускная способность памяти (для обоих вариантов).

3. Не используйте глобальные __m256i константы; компиляторы инициализируют их неэффективно (путем копирования в BSS во время выполнения). В противном случае да, один vpermd — это то, что вы хотите. (Вы могли бы использовать _mm256_castsi256_si128 вместо извлечения, но большинство компиляторов оптимизируют извлечение (v, 0). ) Поскольку вас интересует только младший 128 результата перемешивания, вы могли бы использовать только __m128i элемент управления перемешиванием, поэтому вам не нужно загружать столько постоянных данных. ( _mm256_castsi128_si256 чтобы использовать его с 256-битным перемешиванием.)