Самый быстрый способ распаковать 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
Размер в этом примере для простоты.
1 ответ
Сдвиг / маска могут быть объединены в vpshufb
, Конечно, это означает, что нужно беспокоиться о маске перетасовки, которая должна откуда-то прийти. Если они могут оставаться в регистрах, это не имеет большого значения, если они должны быть загружены, что может убить эту технику.
Это выглядит сомнительно как оптимизация для Intel, так как сдвиг имеет значение output.through 0,5 и AND 0,33, что лучше, чем 1, который вы получили бы с shuffle (процессоры Intel с двумя модулями shuffle не поддерживали AVX2, поэтому они не имеют отношения, поэтому тасование переходит на P5). Это все еще меньше µop, поэтому в контексте другого кода это может или не может стоить делать, в зависимости от того, что является бутылочным горлышком. Если остальная часть кода использует только P01 (типично для FP SIMD), вероятно, хорошая идея - переместить µops в P5.
На Рызене это, как правило, лучше, поскольку векторные сдвиги имеют низкую пропускную способность. 256b vpsrad
генерирует 2 мопа, которые оба должны перейти на порт 2 (а затем еще два мопа для vpand
, но они могут перейти на любой из четырех портов alu), 256b vpshufb
генерирует 2 мкопа, которые могут попасть в порты 1 и 2. С другой стороны, сборка на Райзене настолько плоха, что это всего лишь шум по сравнению с огромным потоком микопс от этого. Вы можете собрать вручную, но тогда все еще будет много мопов, и они, вероятно, перейдут на P12, что делает эту технику плохой.
В заключение я не могу сказать вам, действительно ли это быстрее или нет, это зависит.