Как правило, если вам нужно повторно упаковать результат в 8-битные целые числа, вам лучше либо распаковать с нулем, используя punpcklbw
/ punpckhbw
, либо переупаковать результат, используя packuswb
. Или иногда вы можете замаскировать нечетные и четные байты в отдельные регистры, выполнить вычисления и битовые или результаты вместе.
«Проблема» с _mm256_cvtepu8_epi16
/ vpmovzxbw
заключается в том, чтопересечение (т. е. он принимает входные данные только из нижней 128-битной половины (или памяти), но результат находится в верхней и нижней половине), и не существует (простого) решения для объединения 16-битных значений из разных дорожек обратно водин (до тех пор, пока AVX512 не пересекает полосу с одним регистром инструкции с насыщением или усечением).
В вашем случае вы можете фактически собрать вместе значения d
и s
в один регистр и a
и255-a
значения в другом и использовать vpmaddubsw
для умножения и сложения. Вам необходимо вычесть 128 из значений d
и s
перед упаковкой их вместе, поскольку один аргумент должен быть со знаком int8
. Результат будет отключен на 128*255
, но это может быть компенсировано, особенно если вы все равно добавите 127
для округления. (Если вы этого не сделаете, вы можете добавить 128 к каждому байту после деления (деление со знаком с округлением вниз) и переупаковки.
Непроверенный код, используя ту же подпись, что и ваша попытка:
// https://stackoverflow.com/questions/35285324/how-to-divide-16-bit-integer-by-255-with-using-sse
inline __m256i div255_epu16(__m256i x) {
__m256i mulhi = _mm256_mulhi_epu16(x, _mm256_set1_epi16(0x8081));
return _mm256_srli_epi16(mulhi, 7);
}
int blend_plane_pixels_444_vectorized(uint8_t *__restrict__ d,
uint8_t *__restrict__ s,
uint8_t *__restrict__ sa,
const int pixels)
{
int n = 0; // Return number of component pixels processed.
for (int k = 0; k + 32 <= pixels; k += 32)
{
// Load 32 (unaligned) of d, s, sa
__m256i vecD = _mm256_loadu_si256((__m256i_u *)d);
__m256i vecS = _mm256_loadu_si256((__m256i_u *)s );
__m256i vecA = _mm256_loadu_si256((__m256i_u *)sa);
// subtract 128 from D and S to have them in the signed domain
// subtracting 128 is equivalent ot xor with 128
vecD = _mm256_xor_si256(vecD, _mm256_set1_epi8(0x80));
vecS = _mm256_xor_si256(vecS, _mm256_set1_epi8(0x80));
// calculate 255-a (equivalent to 255 ^ a):
__m256i vecA_ = _mm256_xor_si256(vecA, _mm256_set1_epi8(0xFF));
__m256i vecAA_lo = _mm256_unpacklo_epi8(vecA, vecA_);
__m256i vecSD_lo = _mm256_unpacklo_epi8(vecS, vecD);
__m256i vecAA_hi = _mm256_unpackhi_epi8(vecA, vecA_);
__m256i vecSD_hi = _mm256_unpackhi_epi8(vecS, vecD);
// R = a * (s-128) + (255-a)*(d-128) = a*s + (255-a)*d - 128*255
__m256i vecR_lo = _mm256_maddubs_epi16(vecAA_lo,vecSD_lo);
__m256i vecR_hi = _mm256_maddubs_epi16(vecAA_hi,vecSD_hi);
// shift back to unsigned domain and add 127 for rounding
vecR_lo = _mm256_add_epi16(vecR_lo, _mm256_set1_epi16(127+128*255));
vecR_hi = _mm256_add_epi16(vecR_hi, _mm256_set1_epi16(127+128*255));
// divide (rounding down)
vecR_lo = div255_epu16(vecR_lo);
vecR_hi = div255_epu16(vecR_hi);
// re-join lower and upper half:
__m256i vecResult = _mm256_packus_epi16(vecR_lo, vecR_hi);
// Write data back to memory (unaligned)
_mm256_storeu_si256((__m256i*)d, vecResult);
d += 32;
s += 32;
sa += 32;
n += 32;
}
return n;
}
Godbolt-Link: https://godbolt.org/z/EYzLw2 Обратите внимание, что -march=haswell
или любая архитектура, которую вы хотите поддерживать, имеет решающее значение, потому что в противном случае gcc не будет использовать невыровненные данные в качестве операнда источника памяти. Конечно, применяются общие правила векторизации, т.е. , если у вас есть контроль над выравниванием, предпочтите выделение выровненных данных. А если нет, вы можете очистить первые невыровненные байты (например, от d
), чтобы иметь хотя бы одну загрузку и выровнять хранилище.
Clang развернет цикл (до двух внутренних итераций), что немного улучшит производительность при достаточно большом вводе.