C++ 什么';最快的STERE-3采集指令序列是什么? 问题是:
从内存生成32位元素的STERE-3集合的最有效序列是什么? 如果存储器被安排为:C++ 什么';最快的STERE-3采集指令序列是什么? 问题是:,c++,x86,vectorization,avx2,C++,X86,Vectorization,Avx2,从内存生成32位元素的STERE-3集合的最有效序列是什么? 如果存储器被安排为: MEM = R0 G0 B0 R1 G1 B1 R2 G2 B2 R3 G3 B3 ... 我们希望获得三个YMM寄存器,其中: YMM0 = R0 R1 R2 R3 R4 R5 R6 R7 YMM1 = G0 G1 G2 G3 G4 G5 G6 G7 YMM2 = B0 B1 B2 B3 B4 B5 B6 B7 动机与讨论 标量C代码类似于 template <typename T> T Pro
MEM = R0 G0 B0 R1 G1 B1 R2 G2 B2 R3 G3 B3 ...
我们希望获得三个YMM寄存器,其中:
YMM0 = R0 R1 R2 R3 R4 R5 R6 R7
YMM1 = G0 G1 G2 G3 G4 G5 G6 G7
YMM2 = B0 B1 B2 B3 B4 B5 B6 B7
动机与讨论
标量C代码类似于
template <typename T>
T Process(const T* Input) {
T Result = 0;
for (int i=0; i < 4096; ++i) {
T R = Input[3*i];
T G = Input[3*i+1];
T B = Input[3*i+2];
Result += some_parallelizable_algorithm<T>(R, G, B);
}
return Result;
}
模板
T进程(常数T*输入){
T结果=0;
对于(int i=0;i<4096;++i){
tr=输入[3*i];
T G=输入[3*i+1];
tb=输入[3*i+2];
结果+=一些可并行的算法(R,G,B);
}
返回结果;
}
假设一些可并行化的算法是用intrinsic编写的,并被调整为尽可能快的实现:
template <typename T>
__m256i some_parallelizable_algorithm(__m256i R, __m256i G, __m256i B);
模板
__m256i一些可并行化的算法(m256i R,m256i G,m256i B);
因此T=int32\u T的向量实现可以是:
template <>
int32_t Process<int32_t>(const int32_t* Input) {
__m256i Step = _mm256_set_epi32(0, 1, 2, 3, 4, 5, 6, 7);
__m256i Result = _mm256_setzero_si256();
for (int i=0; i < 4096; i+=8) {
// R = R0 R1 R2 R3 R4 R5 R6 R7
__m256i R = _mm256_i32gather_epi32 (Input+3*i, Step, 3);
// G = G0 G1 G2 G3 G4 G5 G6 G7
__m256i G = _mm256_i32gather_epi32 (Input+3*i+1, Step, 3);
// B = B0 B1 B2 B3 B4 B5 B6 B7
__m256i B = _mm256_i32gather_epi32 (Input+3*i+2, Step, 3);
Result = _mm256_add_epi32 (Result,
some_parallelizable_algorithm<int32_t>(R, G, B));
}
// Here should be the less interesting part:
// Perform a reduction on Result and return the result
}
模板
int32_t进程(常量int32_t*输入){
__m256i步进=_mm256_set_epi32(0、1、2、3、4、5、6、7);
__m256i结果=_mm256_setzero_si256();
对于(int i=0;i<4096;i+=8){
//R=R0 R1 R2 R3 R4 R5 R6 R7
__m256i R=_mm256_i32gather_epi32(输入+3*i,步骤3);
//G=G0 G1 G2 G3 G4 G5 G6 G7
__m256i G=_mm256_i32gather_epi32(输入+3*i+1,步骤3);
//B=B0B1 B2 B3 B4 B5 B6 B7
__m256i B=_mm256_i32gather_epi32(输入+3*i+2,步骤3);
结果=_mm256_add_epi32(结果,
一些可并行的算法(R,G,B);
}
//以下是不太有趣的部分:
//对结果执行缩减并返回结果
}
首先,可以这样做,因为32位元素有聚集指令,但16位元素或8位元素没有。
第二,也是更重要的一点,出于性能原因,应该完全避免上面的聚集指令。使用连续的宽加载并对加载的值进行洗牌以获得R、G和B向量可能更有效
template <>
int32_t Process<int32_t>(const int32_t* Input) {
__m256i Result = _mm256_setzero_si256();
for (int i=0; i < 4096; i+=3) {
__m256i Ld0 = _mm256_lddqu_si256((__m256i*)Input+3*i));
__m256i Ld1 = _mm256_lddqu_si256((__m256i*)Input+3*i+1));
__m256i Ld2 = _mm256_lddqu_si256((__m256i*)Input+3*i+2));
__m256i R = ???
__m256i G = ???
__m256i B = ???
Result = _mm256_add_epi32 (Result,
some_parallelizable_algorithm<int32_t>(R, G, B));
}
// Here should be the less interesting part:
// Perform a reduction on Result and return the result
}
模板
int32_t进程(常量int32_t*输入){
__m256i结果=_mm256_setzero_si256();
对于(int i=0;i<4096;i+=3){
__m256i Ld0=_mm256_lddqu_si256((_m256i*)输入+3*i));
__m256i Ld1=_mm256_lddqu_si256((_m256i*)输入+3*i+1));
__m256i Ld2=_mm256_lddqu_si256((_m256i*)输入+3*i+2));
__m256i R=???
__m256i G=???
__m256i B=???
结果=_mm256_add_epi32(结果,
一些可并行的算法(R,G,B);
}
//以下是不太有趣的部分:
//对结果执行缩减并返回结果
}
似乎对于power-2步幅(2,4,…)有使用UNKPCKL/UNKPCKH的已知方法,但对于步幅-3访问,我找不到任何参考
我对解决T=int32\u T、T=int16\u T和T=int8\u T的问题很感兴趣,但为了保持重点,让我们只讨论第一种情况。描述了如何精确执行您想要的3x8情况
那篇文章介绍了浮动的情况。如果需要int32
,则需要强制转换输出,因为没有整数版本的\u mm256\u shuffle\u 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's and y's
__m256 yz = _mm256_shuffle_ps(m03, m14, _MM_SHUFFLE( 1,0,2,1)); // lower y's 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次洗牌)
在一般情况下,可以在
O(s*log(W))
指令中执行sxw
转置。其中:
是步幅S
是SIMD宽度W
(S x W load-permute) <= S * (lg(W) + 1) instructions
反向3 x 16
转置存储将留给读者作为练习
由于
S=3
有些退化,因此该模式一点也不容易看到。但是如果你能看到这个模式,你就可以把它推广到任意奇数整数S
以及任意二次幂W
8位整数的情况。
正如上面的注释中已经提到的,两条输入洗牌指令,例如vshufps
,不需要
存在8位粒度。因此,8位解决方案与32位解决方案略有不同。下面介绍两种不同的解决方案
一种简单的方法是将8位整数“按颜色(rgb)”与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;
}
不幸的是,
vpblendvb
指令通常相对较慢:
在“英特尔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;
}
将这些方法的思想应用于16位的情况很容易。使用连续的宽负载和洗牌可能更有效。是的,当然,但你是对的,如何做好这一点并不明显利用像shufps和punpckl/h这样的双输入洗牌似乎是个好主意,但可能还需要一些pshufb
和por
。也许我们可以用某种方式屏蔽和使用packuswb,然后用一个混合了R,G和B的向量做些什么?可能不会,因为屏蔽输入以克服饱和是昂贵的,而且mergin
3 vmovdqu
3 vinserti128-load
6 vpblendvb
3 vpshufb
#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