Какова наиболее эффективная последовательность для создания сборника из 3-х битных элементов из памяти?
Если память устроена так:
Скажем, что some_parallelizable_algorithm был написан в intrinsics и был настроен на максимально возможную возможную реализацию:
Во-первых, это можно сделать, потому что есть сбор инструкций для 32-битных элементов, но для 16-битных элементов или 8-битных элементов их нет.
Во-вторых, и, что более важно, вышеприведенную инструкцию по сбору следует полностью исключить по соображениям производительности. Вероятно, более эффективно использовать смежные широкие нагрузки и перетасовывать загруженные значения для получения векторов R, G и B.
Кажется, что для power-2 strides (2, 4,...) существуют известные методы, использующие UNKPCKL/UNKPCKH, но для доступа к stride-3 я не мог найти никаких ссылок.
Я заинтересован в решении этого для T = int32_t, T = int16_t и T = int8_t, но чтобы оставаться сосредоточенным, давайте обсуждать только первый случай.
Ответ 1
В этой статье от Intel описывается, как сделать именно то, что вы хотите. 3/
В этой статье рассматривается случай float
. Если вы хотите int32
, вам нужно будет отображать выходы, так как нет целочисленной версии _mm256_shuffle_ps()
.
![введите описание изображения здесь]()
Скопируйте их решение дословно:
float *p; // address of first vector
__m128 *m = (__m128*) p;
__m256 m03;
__m256 m14;
__m256 m25;
m03 = _mm256_castps128_ps256(m[0]); // load lower halves
m14 = _mm256_castps128_ps256(m[1]);
m25 = _mm256_castps128_ps256(m[2]);
m03 = _mm256_insertf128_ps(m03 ,m[3],1); // load upper halves
m14 = _mm256_insertf128_ps(m14 ,m[4],1);
m25 = _mm256_insertf128_ps(m25 ,m[5],1);
__m256 xy = _mm256_shuffle_ps(m14, m25, _MM_SHUFFLE( 2,1,3,2)); // upper x and y
__m256 yz = _mm256_shuffle_ps(m03, m14, _MM_SHUFFLE( 1,0,2,1)); // lower y and z's
__m256 x = _mm256_shuffle_ps(m03, xy , _MM_SHUFFLE( 2,0,3,0));
__m256 y = _mm256_shuffle_ps(yz , xy , _MM_SHUFFLE( 3,1,2,0));
__m256 z = _mm256_shuffle_ps(yz , m25, _MM_SHUFFLE( 3,0,3,1));
Итак, это 11 инструкций. (6 нагрузок, 5 перетасовки)
В общем случае можно выполнить транспонирование S x W
в инструкциях O(S*log(W))
. Где:
-
S
является шагом
-
W
- это ширина SIMD
Предполагая существование 2-векторных перестановок и полувекторных вставных нагрузок, тогда формула будет выглядеть следующим образом:
(S x W load-permute) <= S * (lg(W) + 1) instructions
Игнорирование изменений reg-reg. Для вырожденных случаев, таких как 3 x 4
, может быть возможно сделать лучше.
Здесь 3 x 16
загрузочный транспортер с AVX512: (6 нагрузок, 3 перетасовки, 6 смесей)
FORCE_INLINE void transpose_f32_16x3_forward_AVX512(
const float T[48],
__m512& r0, __m512& r1, __m512& r2
){
__m512 a0, a1, a2;
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
a0 = _mm512_castps256_ps512(_mm256_loadu_ps(T + 0));
a1 = _mm512_castps256_ps512(_mm256_loadu_ps(T + 8));
a2 = _mm512_castps256_ps512(_mm256_loadu_ps(T + 16));
a0 = _mm512_insertf32x8(a0, ((const __m256*)T)[3], 1);
a1 = _mm512_insertf32x8(a1, ((const __m256*)T)[4], 1);
a2 = _mm512_insertf32x8(a2, ((const __m256*)T)[5], 1);
// 0 1 2 3 4 5 6 7 24 25 26 27 28 29 30 31
// 8 9 10 11 12 13 14 15 32 33 34 35 36 37 38 39
// 16 17 18 19 20 21 22 23 40 41 42 43 44 45 46 47
r0 = _mm512_mask_blend_ps(0xf0f0, a0, a1);
r1 = _mm512_permutex2var_ps(a0, _mm512_setr_epi32( 4, 5, 6, 7, 16, 17, 18, 19, 12, 13, 14, 15, 24, 25, 26, 27), a2);
r2 = _mm512_mask_blend_ps(0xf0f0, a1, a2);
// 0 1 2 3 12 13 14 15 24 25 26 27 36 37 38 39
// 4 5 6 7 16 17 18 19 28 29 30 31 40 41 42 43
// 8 9 10 11 20 21 22 23 32 33 34 35 44 45 46 47
a0 = _mm512_mask_blend_ps(0xcccc, r0, r1);
a1 = _mm512_shuffle_ps(r0, r2, 78);
a2 = _mm512_mask_blend_ps(0xcccc, r1, r2);
// 0 1 6 7 12 13 18 19 24 25 30 31 36 37 42 43
// 2 3 8 9 14 15 20 21 26 27 32 33 38 39 44 45
// 4 5 10 11 16 17 22 23 28 29 34 35 40 41 46 47
r0 = _mm512_mask_blend_ps(0xaaaa, a0, a1);
r1 = _mm512_permutex2var_ps(a0, _mm512_setr_epi32( 1, 16, 3, 18, 5, 20, 7, 22, 9, 24, 11, 26, 13, 28, 15, 30), a2);
r2 = _mm512_mask_blend_ps(0xaaaa, a1, a2);
// 0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45
// 1 4 7 10 13 16 19 22 25 28 31 34 37 40 43 46
// 2 5 8 11 14 17 20 23 26 29 32 35 38 41 44 47
}
Обратное хранилище транспортов 3 x 16
будет оставлено как упражнение для читателя.
Образец вовсе не тривиально видеть, так как S = 3
несколько вырожден. Но если вы можете увидеть шаблон, вы сможете обобщить его на любое нечетное целое число S
, а также на любую из двух-двух W
.
Ответ 2
8-разрядный целочисленный случай.
Как уже упоминалось в комментариях выше, две команды ввода в случайном порядке, такие как vshufps
, не
существуют для 8-битной детализации. Следовательно, 8-битное решение немного отличается от 32-битного решения. Ниже описаны два разных решения.
Прямым подходом является группировка цвета 8-битных целых чисел цветом (R G B) 'с 6 vpblendvb
-s, затем
с помощью перестановки vpshufb
:
#include <stdio.h>
#include <x86intrin.h>
/* gcc -O3 -Wall -m64 -march=broadwell stride_3.c */
int __attribute__ ((noinline)) print_vec_char(__m256i x);
int main() {
char *m;
int i;
__m256i blnd1 = _mm256_set_epi8(0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0, 0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0);
__m256i blnd2 = _mm256_set_epi8(0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0, 0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0);
__m256i p0 = _mm256_set_epi8(13,10,7,4,1, 14,11,8,5,2, 15,12,9,6,3,0, 13,10,7,4,1, 14,11,8,5,2, 15,12,9,6,3,0);
__m256i p1 = _mm256_set_epi8(14,11,8,5,2, 15,12,9,6,3,0, 13,10,7,4,1, 14,11,8,5,2, 15,12,9,6,3,0, 13,10,7,4,1);
__m256i p2 = _mm256_set_epi8(15,12,9,6,3,0, 13,10,7,4,1, 14,11,8,5,2, 15,12,9,6,3,0, 13,10,7,4,1, 14,11,8,5,2);
m = _mm_malloc(96,32);
for(i = 0; i < 96; i++) m[i] = i;
// printf("m_lo ");print_vec_char(_mm256_load_si256((__m256i*)&m[0]));printf("m_mid ");print_vec_char(_mm256_load_si256((__m256i*)&m[32]));printf("m_hi ");print_vec_char(_mm256_load_si256((__m256i*)&m[64]));printf("\n");
// m_lo 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 || 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0
// m_mid 63 62 61 60 | 59 58 57 56 | 55 54 53 52 | 51 50 49 48 || 47 46 45 44 | 43 42 41 40 | 39 38 37 36 | 35 34 33 32
// m_hi 95 94 93 92 | 91 90 89 88 | 87 86 85 84 | 83 82 81 80 || 79 78 77 76 | 75 74 73 72 | 71 70 69 68 | 67 66 65 64
__m256i t0 = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)&m[0]));
__m256i t1 = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)&m[16]));
__m256i t2 = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)&m[32]));
t0 = _mm256_inserti128_si256(t0,_mm_loadu_si128((__m128i*)&m[48]),1);
t1 = _mm256_inserti128_si256(t1,_mm_loadu_si128((__m128i*)&m[64]),1);
t2 = _mm256_inserti128_si256(t2,_mm_loadu_si128((__m128i*)&m[80]),1);
// printf("t0 ");print_vec_char(t0);printf("t1 ");print_vec_char(t1);printf("t2 ");print_vec_char(t2);printf("\n");
// t0 63 62 61 60 | 59 58 57 56 | 55 54 53 52 | 51 50 49 48 || 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0
// t1 79 78 77 76 | 75 74 73 72 | 71 70 69 68 | 67 66 65 64 || 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16
// t2 95 94 93 92 | 91 90 89 88 | 87 86 85 84 | 83 82 81 80 || 47 46 45 44 | 43 42 41 40 | 39 38 37 36 | 35 34 33 32
__m256i u0 = _mm256_blendv_epi8( _mm256_blendv_epi8(t0,t1,blnd2), t2,blnd1);
__m256i u1 = _mm256_blendv_epi8( _mm256_blendv_epi8(t1,t2,blnd2), t0,blnd1);
__m256i u2 = _mm256_blendv_epi8( _mm256_blendv_epi8(t2,t0,blnd2), t1,blnd1);
// printf("u0 ");print_vec_char(u0);printf("u1 ");print_vec_char(u1);printf("u2 ");print_vec_char(u2);printf("\n");
// u0 63 78 93 60 | 75 90 57 72 | 87 54 69 84 | 51 66 81 48 || 15 30 45 12 | 27 42 9 24 | 39 6 21 36 | 3 18 33 0
// u1 79 94 61 76 | 91 58 73 88 | 55 70 85 52 | 67 82 49 64 || 31 46 13 28 | 43 10 25 40 | 7 22 37 4 | 19 34 1 16
// u2 95 62 77 92 | 59 74 89 56 | 71 86 53 68 | 83 50 65 80 || 47 14 29 44 | 11 26 41 8 | 23 38 5 20 | 35 2 17 32
t0 = _mm256_shuffle_epi8(u0,p0);
t1 = _mm256_shuffle_epi8(u1,p1);
t2 = _mm256_shuffle_epi8(u2,p2);
printf("t0 ");print_vec_char(t0);printf("t1 ");print_vec_char(t1);printf("t2 ");print_vec_char(t2);printf("\n");
// t0 93 90 87 84 | 81 78 75 72 | 69 66 63 60 | 57 54 51 48 || 45 42 39 36 | 33 30 27 24 | 21 18 15 12 | 9 6 3 0
// t1 94 91 88 85 | 82 79 76 73 | 70 67 64 61 | 58 55 52 49 || 46 43 40 37 | 34 31 28 25 | 22 19 16 13 | 10 7 4 1
// t2 95 92 89 86 | 83 80 77 74 | 71 68 65 62 | 59 56 53 50 || 47 44 41 38 | 35 32 29 26 | 23 20 17 14 | 11 8 5 2
return 0;
}
int __attribute__ ((noinline)) print_vec_char(__m256i x){
char v[32];
_mm256_storeu_si256((__m256i *)v,x);
printf("%3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi || ",
v[31],v[30],v[29],v[28],v[27],v[26],v[25],v[24],v[23],v[22],v[21],v[20],v[19],v[18],v[17],v[16]);
printf("%3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi \n",
v[15],v[14],v[13],v[12],v[11],v[10],v[9],v[8],v[7],v[6],v[5],v[4],v[3],v[2],v[1],v[0]);
return 0;
}
Краткое описание инструкции:
3 vmovdqu
3 vinserti128-load
6 vpblendvb
3 vpshufb
К сожалению, команда vpblendvb
часто относительно медленная:
на Intel Skylake vpblendvb
имеет пропускную способность по одному за цикл и
на AMD Ryzen и Intel Haswell пропускная способность составляет всего один на два цилиндра.
Skylake-X имеет быструю байтовую смесь vpblendmb
(пропускная способность три за цикл (256 бит)), хотя на Skylake-X может быть больше
заинтересовался решением, которое работает с 512-битными векторами вместо 256-битного.
Альтернативой является объединение vpshufb
с vshufps
, как это было предложено в комментариях @Peter Cordes выше.
В приведенном ниже коде данные загружаются как 12-байтовые фрагменты. В целом требуется больше инструкций, чем в первом решении.
Тем не менее, производительность этого второго решения, вероятно, лучше, чем первое решение, в зависимости от окружающего кода
и микро архитектуры.
#include <stdio.h>
#include <x86intrin.h>
/* gcc -O3 -Wall -m64 -march=broadwell stride_3.c */
int __attribute__ ((noinline)) print_vec_char(__m256i x);
inline __m256i _mm256_shufps_epi32(__m256i a,__m256i b,int imm){return _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(a),_mm256_castsi256_ps(b),imm));}
int main() {
char *m;
int i;
__m256i p0 = _mm256_set_epi8(-1,-1,-1,-1, 11,8,5,2, 10,7,4,1, 9,6,3,0, -1,-1,-1,-1, 11,8,5,2, 10,7,4,1, 9,6,3,0);
__m256i p1 = _mm256_set_epi8(11,8,5,2, 10,7,4,1, 9,6,3,0, -1,-1,-1,-1, 11,8,5,2, 10,7,4,1, 9,6,3,0, -1,-1,-1,-1);
__m256i p2 = _mm256_set_epi8(10,7,4,1, 9,6,3,0, -1,-1,-1,-1, 11,8,5,2, 10,7,4,1, 9,6,3,0,-1, -1,-1,-1, 11,8,5,2);
__m256i p3 = _mm256_set_epi8(9,6,3,0, -1,-1,-1,-1, 11,8,5,2, 10,7,4,1, 9,6,3,0, -1,-1,-1,-1, 11,8,5,2, 10,7,4,1);
m = _mm_malloc(96+4,32); /* 4 extra dummy bytes to avoid errors with _mm_loadu_si128((__m128i*)&m[84]) . Otherwise use maskload instead of standard load */
for(i = 0; i < 96; i++) m[i] = i;
// printf("m_lo ");print_vec_char(_mm256_load_si256((__m256i*)&m[0]));printf("m_mid ");print_vec_char(_mm256_load_si256((__m256i*)&m[32]));printf("m_hi ");print_vec_char(_mm256_load_si256((__m256i*)&m[64]));printf("\n");
// m_lo 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 || 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0
// m_mid 63 62 61 60 | 59 58 57 56 | 55 54 53 52 | 51 50 49 48 || 47 46 45 44 | 43 42 41 40 | 39 38 37 36 | 35 34 33 32
// m_hi 95 94 93 92 | 91 90 89 88 | 87 86 85 84 | 83 82 81 80 || 79 78 77 76 | 75 74 73 72 | 71 70 69 68 | 67 66 65 64
__m256i t0 = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)&m[0]));
__m256i t1 = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)&m[12]));
__m256i t2 = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)&m[24]));
__m256i t3 = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)&m[36]));
t0 = _mm256_inserti128_si256(t0,_mm_loadu_si128((__m128i*)&m[48]),1);
t1 = _mm256_inserti128_si256(t1,_mm_loadu_si128((__m128i*)&m[60]),1);
t2 = _mm256_inserti128_si256(t2,_mm_loadu_si128((__m128i*)&m[72]),1);
t3 = _mm256_inserti128_si256(t3,_mm_loadu_si128((__m128i*)&m[84]),1); /* Use a masked load (_mm_maskload_epi32) here if m[99] is not a valid address */
// printf("t0 ");print_vec_char(t0);printf("t1 ");print_vec_char(t1);printf("t2 ");print_vec_char(t2);printf("t3 ");print_vec_char(t3);printf("\n");
// t0 63 62 61 60 | 59 58 57 56 | 55 54 53 52 | 51 50 49 48 || 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0
// t1 75 74 73 72 | 71 70 69 68 | 67 66 65 64 | 63 62 61 60 || 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12
// t2 87 86 85 84 | 83 82 81 80 | 79 78 77 76 | 75 74 73 72 || 39 38 37 36 | 35 34 33 32 | 31 30 29 28 | 27 26 25 24
// t3 0 0 0 0 | 95 94 93 92 | 91 90 89 88 | 87 86 85 84 || 51 50 49 48 | 47 46 45 44 | 43 42 41 40 | 39 38 37 36
t0 = _mm256_shuffle_epi8(t0,p0);
t1 = _mm256_shuffle_epi8(t1,p1);
t2 = _mm256_shuffle_epi8(t2,p2);
t3 = _mm256_shuffle_epi8(t3,p3);
// printf("t0 ");print_vec_char(t0);printf("t1 ");print_vec_char(t1);printf("t2 ");print_vec_char(t2);printf("t3 ");print_vec_char(t3);printf("\n");
// t0 0 0 0 0 | 59 56 53 50 | 58 55 52 49 | 57 54 51 48 || 0 0 0 0 | 11 8 5 2 | 10 7 4 1 | 9 6 3 0
// t1 71 68 65 62 | 70 67 64 61 | 69 66 63 60 | 0 0 0 0 || 23 20 17 14 | 22 19 16 13 | 21 18 15 12 | 0 0 0 0
// t2 82 79 76 73 | 81 78 75 72 | 0 0 0 0 | 83 80 77 74 || 34 31 28 25 | 33 30 27 24 | 0 0 0 0 | 35 32 29 26
// t3 93 90 87 84 | 0 0 0 0 | 95 92 89 86 | 94 91 88 85 || 45 42 39 36 | 0 0 0 0 | 47 44 41 38 | 46 43 40 37
__m256i u0 = _mm256_blend_epi32(t0,t1,0b10101010);
__m256i u1 = _mm256_blend_epi32(t2,t3,0b10101010);
__m256i u2 = _mm256_blend_epi32(t0,t1,0b01010101);
__m256i u3 = _mm256_blend_epi32(t2,t3,0b01010101);
// printf("u0 ");print_vec_char(u0);printf("u1 ");print_vec_char(u1);printf("u2 ");print_vec_char(u2);printf("u3 ");print_vec_char(u3);printf("\n");
// u0 71 68 65 62 | 59 56 53 50 | 69 66 63 60 | 57 54 51 48 || 23 20 17 14 | 11 8 5 2 | 21 18 15 12 | 9 6 3 0
// u1 93 90 87 84 | 81 78 75 72 | 95 92 89 86 | 83 80 77 74 || 45 42 39 36 | 33 30 27 24 | 47 44 41 38 | 35 32 29 26
// u2 0 0 0 0 | 70 67 64 61 | 58 55 52 49 | 0 0 0 0 || 0 0 0 0 | 22 19 16 13 | 10 7 4 1 | 0 0 0 0
// u3 82 79 76 73 | 0 0 0 0 | 0 0 0 0 | 94 91 88 85 || 34 31 28 25 | 0 0 0 0 | 0 0 0 0 | 46 43 40 37
t0 = _mm256_blend_epi32(u0,u1,0b11001100);
t1 = _mm256_shufps_epi32(u2,u3,0b00111001);
t2 = _mm256_shufps_epi32(u0,u1,0b01001110);
printf("t0 ");print_vec_char(t0);printf("t1 ");print_vec_char(t1);printf("t2 ");print_vec_char(t2);printf("\n");
// t0 93 90 87 84 | 81 78 75 72 | 69 66 63 60 | 57 54 51 48 || 45 42 39 36 | 33 30 27 24 | 21 18 15 12 | 9 6 3 0
// t1 94 91 88 85 | 82 79 76 73 | 70 67 64 61 | 58 55 52 49 || 46 43 40 37 | 34 31 28 25 | 22 19 16 13 | 10 7 4 1
// t2 95 92 89 86 | 83 80 77 74 | 71 68 65 62 | 59 56 53 50 || 47 44 41 38 | 35 32 29 26 | 23 20 17 14 | 11 8 5 2
return 0;
}
int __attribute__ ((noinline)) print_vec_char(__m256i x){
char v[32];
_mm256_storeu_si256((__m256i *)v,x);
printf("%3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi || ",
v[31],v[30],v[29],v[28],v[27],v[26],v[25],v[24],v[23],v[22],v[21],v[20],v[19],v[18],v[17],v[16]);
printf("%3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi | %3hhi %3hhi %3hhi %3hhi \n",
v[15],v[14],v[13],v[12],v[11],v[10],v[9],v[8],v[7],v[6],v[5],v[4],v[3],v[2],v[1],v[0]);
return 0;
}
Краткое описание инструкции:
4 vmovdqu
4 vinserti128-load
4 vpshufb
5 vpblendd (vpblendd is much faster than vpblendvb on most cpu architectures)
2 vshufps
Легко адаптировать идеи этих методов к 16-разрядному случаю.