Самый быстрый способ распаковать 8-битные из 32-битных значений (__m256i) в __m256 с помощью AVX2

У меня есть array под названием A, который содержит 32 unsigned char значения.

Я хочу распаковать эти значения в 4 __m256 переменных с помощью этого правила, предполагая, что у нас есть индекс от 0 до 31 относительно всех значений из A, распакованные 4 переменные будут иметь следующие значения:

B_0 = A[0], A[4],  A[8], A[12], A[16], A[20], A[24], A[28]
B_1 = A[1], A[5],  A[9], A[13], A[17], A[21], A[25], A[29]
B_2 = A[2], A[6], A[10], A[14], A[18], A[22], A[26], A[30]
B_3 = A[3], A[7], A[11], A[15], A[19], A[23], A[27], A[31]

Для этого у меня есть этот код:

const auto mask = _mm256_set1_epi32( 0x000000FF );
...
const auto A_values = _mm256_i32gather_epi32(reinterpret_cast<const int*>(A.data(), A_positions.values_, 4);

// This code bellow is equivalent to B_0 = static_cast<float>((A_value >> 24) & 0x000000FF)
const auto B_0 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 24), mask));
const auto B_1 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 16), mask));
const auto B_2 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 8), mask));
const auto B_3 = _mm256_cvtepi32_ps(_mm256_and_si256(_mm256_srai_epi32(A_values, 0), mask));

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

Кроме того, просто для пояснения я сказал, что array A имеет размер 32, но это неправда, этот массив содержит намного больше значений, и мне нужно получить доступ к его элементам с разных позиций (но всегда из блоков по 4 uint8_t), поэтому я используйте _mm256_i32gather_epi23 для получения этих значений. Я просто ограничиваю размер array в этом примере для простоты.


person E. B.    schedule 10.08.2017    source источник
comment
Очевидно, что сдвиг на 0 можно удалить, Clang делает это автоматически, а GCC и MSVC - нет.   -  person harold    schedule 10.08.2017
comment
С _mm256_srai_epi32(A_values, 24) нет необходимости маскировать старшие биты двоичным and 0x000000FF, потому что они уже равны 0.   -  person wim    schedule 11.08.2017
comment
@wim также измените его на _mm256_srli_epi32, затем   -  person harold    schedule 11.08.2017
comment
@harold Да, логический сдвиг вместо арифметического. Я не заметил a в _mm256_srai_epi32.   -  person wim    schedule 11.08.2017
comment
Установлен ли порядок вывода? В противном случае альтернативой может быть использование _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(...)).   -  person chtz    schedule 11.08.2017


Ответы (1)


Сдвиг / маска могут быть объединены в vpshufb. Конечно, это означает, что нужно беспокоиться о маске тасования, которая должна откуда-то взяться. Если они могут оставаться в регистрах, это не проблема, если их нужно загружать, это может убить эту технику.

Это кажется сомнительным с точки зрения оптимизации Intel, поскольку сдвиг имеет пропускную способность 0,5 и И 0,33, что лучше, чем 1, который вы получили бы при перемешивании (процессоры Intel с двумя модулями перемешивания не поддерживали AVX2, поэтому они не актуальны, поэтому перетасовка переходит к P5). Это все еще меньше микропрограмм, поэтому в контексте другого кода это может быть, а может и не стоить того, в зависимости от того, что является узким местом. Если в остальной части кода используется только P01 (типично для FP SIMD), перенос микропрограмм на P5, вероятно, будет хорошей идеей.

На Ryzen это обычно лучше, поскольку у векторных сдвигов там низкая пропускная способность. 256b vpsrad генерирует 2 мкоп, которые оба должны идти на порт 2 (а затем еще два мкопа для vpand, но они могут переходить на любой из четырех портов alu), 256b vpshufb генерирует 2 мкоп, которые могут идти на порты 1 и 2 С другой стороны, функция gather на Ryzen настолько плоха, что это всего лишь шум по сравнению с огромным потоком микопов от этого. Вы можете собирать вручную, но тогда это все еще много микропроцессоров, и они, скорее всего, перейдут на P12, что делает эту технику плохой.

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

person harold    schedule 10.08.2017
comment
Привет, Гарольд, спасибо за ответ, я думаю, что перемешивание - отличный способ улучшить его, но я новичок в мире SIMD, поэтому, если возможно, вы можете показать мне пример того, как я бы использовал его для получения такое же значение моих операций сдвига / маски? Кроме того, сравнивая свой код, я обнаружил, что если я удалю части & detail::mask (в моем реальном коде переменная маски находится внутри пространства имен detail перед моей функцией), мой код будет работать в 3 раза быстрее. Как вы сказали в своем ответе, оператор AND должен работать очень быстро. Не уверен, почему это так медленно, возможно, маска по какой-то причине не выровнена .. - person E. B.; 11.08.2017
comment
@ E.B. перемешайте с маской типа _mm256_setr(1, -1, -1, -1, 5, -1, -1, -1, 9, ... для той, которая была сдвинута вправо на 8. Значения -1 обнуляют на выходе, другие числа получают байт с этим индексом. Такая разница в скорости звучит немного странно, возможно, маска каждый раз перезагружается из памяти (обычно этого следует избегать, если возможно). - person harold; 11.08.2017