Как эффективно переупорядочить байты вектора __m256i (преобразовать int32_t в uint8_t)? - PullRequest
2 голосов
/ 25 апреля 2019

Мне нужно оптимизировать следующую операцию сжатия (на сервере с доступными инструкциями AVX2):

взять экспоненты массива с плавающей точкой, сместить и сохранить в массив uint8_t

Iу меня мало опыта, и мне предложили начать с https://github.com/feltor-dev/vcl library

сейчас, когда у меня есть

uint8_t* uin8_t_ptr = ...;
float* float_ptr = ...;
float* final_ptr = float_ptr + offset;

for (; float_ptr < final_ptr; float_ptr+=8) {
    Vec8f vec_f = Vec8f().load(float_ptr);
    Vec8i vec_i = fraction(vec_f) + 128; // range: 0~255
    ...
}

Мой вопрос: как эффективно сохранить результаты vec_i в массиве uint8_t?

Я не смог найти соответствующие функции в библиотеке vcl и пытался изучить внутренние инструкции, так как мог получить доступ к данным __m256i.

В настоящее время я понимаю, что нужно использовать что-то вроде _mm256_shuffle_epi8, ноНе знаю, как сделать это эффективно.

Интересно, можно ли попытаться использовать биты и хранить 32 элемента каждый раз (используя цикл с float_ptr + = 32).

Любые предложения приветствуются.Благодаря.

1 Ответ

3 голосов
/ 25 апреля 2019

Вероятно, ваша лучшая ставка для векторизации этого может быть с vpackssdw / vpackuswb и vpermd как исправление пересечения полосы после пакета в полосе.

  • _mm256_srli_epi32, чтобы сместить показатель степени (и знаковый бит) в нижнюю часть каждого 32-разрядного элемента.Логический сдвиг оставляет неотрицательный результат независимо от знакового бита.
  • Затем упаковывает пары векторов до 16-бит с помощью _mm256_packs_epi32 (знаковый вход, знаковая насыщенность выхода).
  • Затем маскирует знаковый бит , оставляя 8-битную экспоненту.Мы ждем до сих пор, поэтому мы можем сделать 16x uint16_t элементов на инструкцию вместо 8x uint32_t.Теперь у вас есть 16-битные элементы, содержащие значения, которые помещаются в uint8_t без переполнения.
  • Затем упакуйте пары векторов до 8-бит с помощью _mm256_packus_epi16 (вход со знаком, без знака насыщенность выхода).Это действительно важно, packs будет обрезать некоторые допустимые значения, потому что ваши данные используют полный диапазон uint8_t.
  • VPERMD , чтобы перетасовать восемь 32-битных фрагментов этого вектора, которые пришлис каждой дорожки 4х 256-битных входных векторов.Точно такой же __m256i lanefix = _mm256_permutevar8x32_epi32(abcd, _mm256_setr_epi32(0,4, 1,5, 2,6, 3,7)); shuffle, как и в Как преобразовать 32-битный float в 8-битный символ со знаком? , который делает тот же пакет после использования преобразования FP-> int вместо shift-shift для захватаполе экспоненты.

Для каждого вектора результатов у вас есть 4x load + shift (vpsrld ymm,[mem], мы надеемся), 2x vpackssdw shuffles, 2x vpand mask, 1x vpackuswb и1x vpermd.Это 4 шаффла, поэтому лучшее, на что мы можем надеяться на Intel HSW / SKL, - это 1 вектор результата на 4 такта.(Ryzen имеет лучшую пропускную способность, кроме vpermd, что дорого.)

Но это должно быть достижимо, так что в среднем 32 байта ввода / 8 байтов вывода за такт.

10 полных векторных ALU-мопов (включая нагрузку с микроплавлением + ALU), и 1 хранилище должно быть в состоянии выполнить в это время.У нас есть место для 16 полных мопов, включая накладные расходы на петли, прежде чем внешний интерфейс станет худшим узким местом, чем тасования.

обновление: упс, Я забыл сосчитать, смещая показатель степени;это займет дополнительно add.Но вы можете сделать это после упаковки до 8-битного. (и оптимизировать его до XOR).Я не думаю, что мы можем оптимизировать это или что-то еще, например, замаскировать знаковый бит.

С AVX512BW вы можете сделать байтовую гранулярность vpaddb, чтобы разбить, с нулевым маскированием, чтобыОбнулить старший байт каждой пары.Это сложило бы смещение в 16-битное маскирование.


AVX512F также имеет vpmovdb 32-> 8-битное усечение (без насыщения),но только для одиночных входов.Таким образом, вы получите один 64-битный или 128-битный результат из одного входного 256 или 512-битного вектора с 1 случайным числом + 1 добавлением на вход вместо 2 + 1 случайных чисел + 2 с нулевой маской vpaddb на каждый входной вектор.(Оба требуют сдвига вправо для каждого входного вектора, чтобы выровнять 8-битное поле экспоненты с байтовой границей внизу двойного слова)

С AVX512VBMI, vpermt2b позволит нам получить байты из 2 входных векторов.Но на CannonLake это стоит 2 мопа, поэтому полезно только для гипотетических будущих процессоров, если оно станет дешевле.Они могут быть старшим байтом слова, поэтому мы могли бы начать с vpaddd вектора до самого смещения влево на 1. Но мы, вероятно, лучше всего с левым сдвигом, потому что кодировка EVEX vpslld или vpsrld может извлекать данные из памяти с немедленным счетом сдвига, в отличие от кодировки VEX.Надеемся, что мы получим один микросинхронизированный uop load + shift для экономии полосы пропускания внешнего интерфейса.


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

А для смешивания байтовой гранулярности (без AVX512BW) требуется vpblendvb, что составляет 2 моп.(А на Haswell работает только на 5-м порту, что потенциально является огромным узким местом. На SKL это 2 моп для любого векторного порта ALU.)

...