通过制作4个4x4矩阵并转置每个矩阵,可以实现8x8矩阵的转置。 这不是我想要的。
在另一个问题中,一个答案gave a solution只需要24个8x8矩阵指令。但是,这不适用于花车。
由于AVX2包含256位寄存器,因此每个寄存器适合8个32位整数(浮点数)。但问题是:
如何使用AVX / AVX2转换8x8浮点矩阵,尽可能使用最小的指令?
答案 0 :(得分:16)
我已经回答了这个问题Fast memory transpose with SSE, AVX, and OpenMP。
让我重复一下使用AVX转置8x8浮点矩阵的解决方案。如果这比使用4x4块和_MM_TRANSPOSE4_PS
更快,请告诉我。我在一个更大的矩阵转置中使用它来进行内存绑定,这可能不是一个公平的测试。
inline void transpose8_ps(__m256 &row0, __m256 &row1, __m256 &row2, __m256 &row3, __m256 &row4, __m256 &row5, __m256 &row6, __m256 &row7) {
__m256 __t0, __t1, __t2, __t3, __t4, __t5, __t6, __t7;
__m256 __tt0, __tt1, __tt2, __tt3, __tt4, __tt5, __tt6, __tt7;
__t0 = _mm256_unpacklo_ps(row0, row1);
__t1 = _mm256_unpackhi_ps(row0, row1);
__t2 = _mm256_unpacklo_ps(row2, row3);
__t3 = _mm256_unpackhi_ps(row2, row3);
__t4 = _mm256_unpacklo_ps(row4, row5);
__t5 = _mm256_unpackhi_ps(row4, row5);
__t6 = _mm256_unpacklo_ps(row6, row7);
__t7 = _mm256_unpackhi_ps(row6, row7);
__tt0 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(1,0,1,0));
__tt1 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(3,2,3,2));
__tt2 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(1,0,1,0));
__tt3 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(3,2,3,2));
__tt4 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(1,0,1,0));
__tt5 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(3,2,3,2));
__tt6 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(1,0,1,0));
__tt7 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(3,2,3,2));
row0 = _mm256_permute2f128_ps(__tt0, __tt4, 0x20);
row1 = _mm256_permute2f128_ps(__tt1, __tt5, 0x20);
row2 = _mm256_permute2f128_ps(__tt2, __tt6, 0x20);
row3 = _mm256_permute2f128_ps(__tt3, __tt7, 0x20);
row4 = _mm256_permute2f128_ps(__tt0, __tt4, 0x31);
row5 = _mm256_permute2f128_ps(__tt1, __tt5, 0x31);
row6 = _mm256_permute2f128_ps(__tt2, __tt6, 0x31);
row7 = _mm256_permute2f128_ps(__tt3, __tt7, 0x31);
}
基于this comment我了解到有更有效的方法可以进行8x8转置。请参阅Intel optimization manual下" 11.11处理端口5压力"中的示例11-19和11-20。例11-19使用相同数量的指令,但通过使用去往port0的混合减少了port5的压力。我可能会在某些时候使用内在函数实现这一点,但此时我并不需要这样做。
我更仔细地看了上面提到的英特尔手册中的例11-19和11-20。事实证明,示例11-19使用了超过必要的4次shuffle操作。它有8个解包,12个shuffle和8个128位permute。我的方法使用少4次shuffle。他们用混合物取代了8个洗牌。所以4次洗牌和8次混合。我怀疑这比我的方法更好,只有八次洗牌。
但是,如果需要从内存加载矩阵,则示例11-20是一种改进。这使用8个解压缩,8个插入,8个shuffle,8个128位负载和8个存储。 128位负载降低了端口压力。我继续使用内在函数来实现它。
//Example 11-20. 8x8 Matrix Transpose Using VINSERTF128 loads
void tran(float* mat, float* matT) {
__m256 r0, r1, r2, r3, r4, r5, r6, r7;
__m256 t0, t1, t2, t3, t4, t5, t6, t7;
r0 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[0*8+0])), _mm_load_ps(&mat[4*8+0]), 1);
r1 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[1*8+0])), _mm_load_ps(&mat[5*8+0]), 1);
r2 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[2*8+0])), _mm_load_ps(&mat[6*8+0]), 1);
r3 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[3*8+0])), _mm_load_ps(&mat[7*8+0]), 1);
r4 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[0*8+4])), _mm_load_ps(&mat[4*8+4]), 1);
r5 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[1*8+4])), _mm_load_ps(&mat[5*8+4]), 1);
r6 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[2*8+4])), _mm_load_ps(&mat[6*8+4]), 1);
r7 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[3*8+4])), _mm_load_ps(&mat[7*8+4]), 1);
t0 = _mm256_unpacklo_ps(r0,r1);
t1 = _mm256_unpackhi_ps(r0,r1);
t2 = _mm256_unpacklo_ps(r2,r3);
t3 = _mm256_unpackhi_ps(r2,r3);
t4 = _mm256_unpacklo_ps(r4,r5);
t5 = _mm256_unpackhi_ps(r4,r5);
t6 = _mm256_unpacklo_ps(r6,r7);
t7 = _mm256_unpackhi_ps(r6,r7);
r0 = _mm256_shuffle_ps(t0,t2, 0x44);
r1 = _mm256_shuffle_ps(t0,t2, 0xEE);
r2 = _mm256_shuffle_ps(t1,t3, 0x44);
r3 = _mm256_shuffle_ps(t1,t3, 0xEE);
r4 = _mm256_shuffle_ps(t4,t6, 0x44);
r5 = _mm256_shuffle_ps(t4,t6, 0xEE);
r6 = _mm256_shuffle_ps(t5,t7, 0x44);
r7 = _mm256_shuffle_ps(t5,t7, 0xEE);
_mm256_store_ps(&matT[0*8], r0);
_mm256_store_ps(&matT[1*8], r1);
_mm256_store_ps(&matT[2*8], r2);
_mm256_store_ps(&matT[3*8], r3);
_mm256_store_ps(&matT[4*8], r4);
_mm256_store_ps(&matT[5*8], r5);
_mm256_store_ps(&matT[6*8], r6);
_mm256_store_ps(&matT[7*8], r7);
}
所以我再次调查了例子11-19。据我所知,基本思路是两个shuffle指令(shufps)可以被一个shuffle和两个混合替换。例如
r0 = _mm256_shuffle_ps(t0,t2, 0x44);
r1 = _mm256_shuffle_ps(t0,t2, 0xEE);
可以替换为
v = _mm256_shuffle_ps(t0,t2, 0x4E);
r0 = _mm256_blend_ps(t0, v, 0xCC);
r1 = _mm256_blend_ps(t2, v, 0x33);
这解释了为什么我的原始代码使用了8次shuffle而示例11-19使用了4次shuffle和8次混合。
混合物对吞吐量有好处,因为混洗只会到达一个端口(在随机端口上造成瓶颈),但混合可以在多个端口上运行,因此无法竞争。但更好的是:8次洗牌或4次洗牌和8次混合?
必须对此进行测试,并且可能依赖于周围的代码。如果您在循环中不需要端口5的其他uops的批次主要用于总uop吞吐量的瓶颈,那么您可能会选择纯shuffle版本。理想情况下,您应该在存储它之前对转置数据进行一些计算,而它已经在寄存器中。请参阅https://agner.org/optimize/中的the x86 tag wiki和其他效果链接。
但是,我没有看到用混合替换解包指令的方法。
这是完整的代码,它结合了实例11-19,将2个shuffle转换为1个shuffle和两个混合,以及使用vinsertf128
加载的例11-20(在Intel Haswell / Skylake CPU上使用2个uop:一个ALU用于任何端口,一个内存。遗憾的是,它们没有微融合。vinsertf128
所有寄存器操作数对于Intel上的shuffle端口都是1 uop,所以这很好,因为编译器将负载折叠成内存操作数for vinsertf128
。)这样做的好处是只需要对齐16字节的源数据以获得最大性能,避免任何缓存行拆分。
#include <stdio.h>
#include <x86intrin.h>
#include <omp.h>
/*
void tran(float* mat, float* matT) {
__m256 r0, r1, r2, r3, r4, r5, r6, r7;
__m256 t0, t1, t2, t3, t4, t5, t6, t7;
r0 = _mm256_load_ps(&mat[0*8]);
r1 = _mm256_load_ps(&mat[1*8]);
r2 = _mm256_load_ps(&mat[2*8]);
r3 = _mm256_load_ps(&mat[3*8]);
r4 = _mm256_load_ps(&mat[4*8]);
r5 = _mm256_load_ps(&mat[5*8]);
r6 = _mm256_load_ps(&mat[6*8]);
r7 = _mm256_load_ps(&mat[7*8]);
t0 = _mm256_unpacklo_ps(r0, r1);
t1 = _mm256_unpackhi_ps(r0, r1);
t2 = _mm256_unpacklo_ps(r2, r3);
t3 = _mm256_unpackhi_ps(r2, r3);
t4 = _mm256_unpacklo_ps(r4, r5);
t5 = _mm256_unpackhi_ps(r4, r5);
t6 = _mm256_unpacklo_ps(r6, r7);
t7 = _mm256_unpackhi_ps(r6, r7);
r0 = _mm256_shuffle_ps(t0,t2,_MM_SHUFFLE(1,0,1,0));
r1 = _mm256_shuffle_ps(t0,t2,_MM_SHUFFLE(3,2,3,2));
r2 = _mm256_shuffle_ps(t1,t3,_MM_SHUFFLE(1,0,1,0));
r3 = _mm256_shuffle_ps(t1,t3,_MM_SHUFFLE(3,2,3,2));
r4 = _mm256_shuffle_ps(t4,t6,_MM_SHUFFLE(1,0,1,0));
r5 = _mm256_shuffle_ps(t4,t6,_MM_SHUFFLE(3,2,3,2));
r6 = _mm256_shuffle_ps(t5,t7,_MM_SHUFFLE(1,0,1,0));
r7 = _mm256_shuffle_ps(t5,t7,_MM_SHUFFLE(3,2,3,2));
t0 = _mm256_permute2f128_ps(r0, r4, 0x20);
t1 = _mm256_permute2f128_ps(r1, r5, 0x20);
t2 = _mm256_permute2f128_ps(r2, r6, 0x20);
t3 = _mm256_permute2f128_ps(r3, r7, 0x20);
t4 = _mm256_permute2f128_ps(r0, r4, 0x31);
t5 = _mm256_permute2f128_ps(r1, r5, 0x31);
t6 = _mm256_permute2f128_ps(r2, r6, 0x31);
t7 = _mm256_permute2f128_ps(r3, r7, 0x31);
_mm256_store_ps(&matT[0*8], t0);
_mm256_store_ps(&matT[1*8], t1);
_mm256_store_ps(&matT[2*8], t2);
_mm256_store_ps(&matT[3*8], t3);
_mm256_store_ps(&matT[4*8], t4);
_mm256_store_ps(&matT[5*8], t5);
_mm256_store_ps(&matT[6*8], t6);
_mm256_store_ps(&matT[7*8], t7);
}
*/
void tran(float* mat, float* matT) {
__m256 r0, r1, r2, r3, r4, r5, r6, r7;
__m256 t0, t1, t2, t3, t4, t5, t6, t7;
r0 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[0*8+0])), _mm_load_ps(&mat[4*8+0]), 1);
r1 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[1*8+0])), _mm_load_ps(&mat[5*8+0]), 1);
r2 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[2*8+0])), _mm_load_ps(&mat[6*8+0]), 1);
r3 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[3*8+0])), _mm_load_ps(&mat[7*8+0]), 1);
r4 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[0*8+4])), _mm_load_ps(&mat[4*8+4]), 1);
r5 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[1*8+4])), _mm_load_ps(&mat[5*8+4]), 1);
r6 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[2*8+4])), _mm_load_ps(&mat[6*8+4]), 1);
r7 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_load_ps(&mat[3*8+4])), _mm_load_ps(&mat[7*8+4]), 1);
t0 = _mm256_unpacklo_ps(r0,r1);
t1 = _mm256_unpackhi_ps(r0,r1);
t2 = _mm256_unpacklo_ps(r2,r3);
t3 = _mm256_unpackhi_ps(r2,r3);
t4 = _mm256_unpacklo_ps(r4,r5);
t5 = _mm256_unpackhi_ps(r4,r5);
t6 = _mm256_unpacklo_ps(r6,r7);
t7 = _mm256_unpackhi_ps(r6,r7);
__m256 v;
//r0 = _mm256_shuffle_ps(t0,t2, 0x44);
//r1 = _mm256_shuffle_ps(t0,t2, 0xEE);
v = _mm256_shuffle_ps(t0,t2, 0x4E);
r0 = _mm256_blend_ps(t0, v, 0xCC);
r1 = _mm256_blend_ps(t2, v, 0x33);
//r2 = _mm256_shuffle_ps(t1,t3, 0x44);
//r3 = _mm256_shuffle_ps(t1,t3, 0xEE);
v = _mm256_shuffle_ps(t1,t3, 0x4E);
r2 = _mm256_blend_ps(t1, v, 0xCC);
r3 = _mm256_blend_ps(t3, v, 0x33);
//r4 = _mm256_shuffle_ps(t4,t6, 0x44);
//r5 = _mm256_shuffle_ps(t4,t6, 0xEE);
v = _mm256_shuffle_ps(t4,t6, 0x4E);
r4 = _mm256_blend_ps(t4, v, 0xCC);
r5 = _mm256_blend_ps(t6, v, 0x33);
//r6 = _mm256_shuffle_ps(t5,t7, 0x44);
//r7 = _mm256_shuffle_ps(t5,t7, 0xEE);
v = _mm256_shuffle_ps(t5,t7, 0x4E);
r6 = _mm256_blend_ps(t5, v, 0xCC);
r7 = _mm256_blend_ps(t7, v, 0x33);
_mm256_store_ps(&matT[0*8], r0);
_mm256_store_ps(&matT[1*8], r1);
_mm256_store_ps(&matT[2*8], r2);
_mm256_store_ps(&matT[3*8], r3);
_mm256_store_ps(&matT[4*8], r4);
_mm256_store_ps(&matT[5*8], r5);
_mm256_store_ps(&matT[6*8], r6);
_mm256_store_ps(&matT[7*8], r7);
}
int verify(float *mat) {
int i,j;
int error = 0;
for(i=0; i<8; i++) {
for(j=0; j<8; j++) {
if(mat[j*8+i] != 1.0f*i*8+j) error++;
}
}
return error;
}
void print_mat(float *mat) {
int i,j;
for(i=0; i<8; i++) {
for(j=0; j<8; j++) printf("%2.0f ", mat[i*8+j]);
puts("");
}
puts("");
}
int main(void) {
int i,j, rep;
float mat[64] __attribute__((aligned(64)));
float matT[64] __attribute__((aligned(64)));
double dtime;
rep = 10000000;
for(i=0; i<64; i++) mat[i] = i;
print_mat(mat);
tran(mat,matT);
//dtime = -omp_get_wtime();
//tran(mat, matT, rep);
//dtime += omp_get_wtime();
printf("errors %d\n", verify(matT));
//printf("dtime %f\n", dtime);
print_mat(matT);
}
答案 1 :(得分:8)
这是一款适用于8 x 8 32位整数的AVX2解决方案。如果你想转换8 x 8浮点数,你当然可以将浮点数向量转换为int和back。也可能只为浮标做一个AVX版本(即不需要AVX2),但我还没有尝试过。
//
// tranpose_8_8_avx2.c
//
#include <stdio.h>
#include <immintrin.h>
#define V_ELEMS 8
static inline void _mm256_merge_epi32(const __m256i v0, const __m256i v1, __m256i *vl, __m256i *vh)
{
__m256i va = _mm256_permute4x64_epi64(v0, _MM_SHUFFLE(3, 1, 2, 0));
__m256i vb = _mm256_permute4x64_epi64(v1, _MM_SHUFFLE(3, 1, 2, 0));
*vl = _mm256_unpacklo_epi32(va, vb);
*vh = _mm256_unpackhi_epi32(va, vb);
}
static inline void _mm256_merge_epi64(const __m256i v0, const __m256i v1, __m256i *vl, __m256i *vh)
{
__m256i va = _mm256_permute4x64_epi64(v0, _MM_SHUFFLE(3, 1, 2, 0));
__m256i vb = _mm256_permute4x64_epi64(v1, _MM_SHUFFLE(3, 1, 2, 0));
*vl = _mm256_unpacklo_epi64(va, vb);
*vh = _mm256_unpackhi_epi64(va, vb);
}
static inline void _mm256_merge_si128(const __m256i v0, const __m256i v1, __m256i *vl, __m256i *vh)
{
*vl = _mm256_permute2x128_si256(v0, v1, _MM_SHUFFLE(0, 2, 0, 0));
*vh = _mm256_permute2x128_si256(v0, v1, _MM_SHUFFLE(0, 3, 0, 1));
}
//
// Transpose_8_8
//
// in place transpose of 8 x 8 int array
//
static void Transpose_8_8(
__m256i *v0,
__m256i *v1,
__m256i *v2,
__m256i *v3,
__m256i *v4,
__m256i *v5,
__m256i *v6,
__m256i *v7)
{
__m256i w0, w1, w2, w3, w4, w5, w6, w7;
__m256i x0, x1, x2, x3, x4, x5, x6, x7;
_mm256_merge_epi32(*v0, *v1, &w0, &w1);
_mm256_merge_epi32(*v2, *v3, &w2, &w3);
_mm256_merge_epi32(*v4, *v5, &w4, &w5);
_mm256_merge_epi32(*v6, *v7, &w6, &w7);
_mm256_merge_epi64(w0, w2, &x0, &x1);
_mm256_merge_epi64(w1, w3, &x2, &x3);
_mm256_merge_epi64(w4, w6, &x4, &x5);
_mm256_merge_epi64(w5, w7, &x6, &x7);
_mm256_merge_si128(x0, x4, v0, v1);
_mm256_merge_si128(x1, x5, v2, v3);
_mm256_merge_si128(x2, x6, v4, v5);
_mm256_merge_si128(x3, x7, v6, v7);
}
int main(void)
{
int32_t buff[V_ELEMS][V_ELEMS] __attribute__ ((aligned(32)));
int i, j;
int k = 0;
// init buff
for (i = 0; i < V_ELEMS; ++i)
{
for (j = 0; j < V_ELEMS; ++j)
{
buff[i][j] = k++;
}
}
// print buff
printf("\nBEFORE:\n");
for (i = 0; i < V_ELEMS; ++i)
{
for (j = 0; j < V_ELEMS; ++j)
{
printf("%4d", buff[i][j]);
}
printf("\n");
}
// transpose
Transpose_8_8((__m256i *)buff[0], (__m256i *)buff[1], (__m256i *)buff[2], (__m256i *)buff[3], (__m256i *)buff[4], (__m256i *)buff[5], (__m256i *)buff[6], (__m256i *)buff[7]);
// print buff
printf("\nAFTER:\n");
for (i = 0; i < V_ELEMS; ++i)
{
for (j = 0; j < V_ELEMS; ++j)
{
printf("%4d", buff[i][j]);
}
printf("\n");
}
// transpose
Transpose_8_8((__m256i *)buff[0], (__m256i *)buff[1], (__m256i *)buff[2], (__m256i *)buff[3], (__m256i *)buff[4], (__m256i *)buff[5], (__m256i *)buff[6], (__m256i *)buff[7]);
// print buff
printf("\nAFTER x2:\n");
for (i = 0; i < V_ELEMS; ++i)
{
for (j = 0; j < V_ELEMS; ++j)
{
printf("%4d", buff[i][j]);
}
printf("\n");
}
return 0;
}
Transpose_8_8
使用clang编写大约56条指令,包括加载和存储 - 我认为应该可以通过更多努力来改进这一点。
编译和测试:
$ gcc -Wall -mavx2 -O3 transpose_8_8_avx2.c && ./a.out
BEFORE:
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
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
AFTER:
0 8 16 24 32 40 48 56
1 9 17 25 33 41 49 57
2 10 18 26 34 42 50 58
3 11 19 27 35 43 51 59
4 12 20 28 36 44 52 60
5 13 21 29 37 45 53 61
6 14 22 30 38 46 54 62
7 15 23 31 39 47 55 63
AFTER x2:
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
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
$
答案 2 :(得分:3)
我决定对一个苹果与一个苹果进行比较,对3种不同的例程进行全面测试。
// transpose.cpp :
/*
Transpose8x8Shuff 100,000,000 times
(0.750000 seconds).
Transpose8x8Permute 100,000,000 times
(0.749000 seconds).
Transpose8x8Insert 100,000,000 times
(0.858000 seconds).
*/
#include <stdio.h>
#include <time.h>
#include <thread>
#include <chrono>
#include <xmmintrin.h>
#include <emmintrin.h>
#include <tmmintrin.h>
#include <immintrin.h>
inline void Transpose8x8Shuff(unsigned long *in)
{
__m256 *inI = reinterpret_cast<__m256 *>(in);
__m256 rI[8];
rI[0] = _mm256_unpacklo_ps(inI[0], inI[1]);
rI[1] = _mm256_unpackhi_ps(inI[0], inI[1]);
rI[2] = _mm256_unpacklo_ps(inI[2], inI[3]);
rI[3] = _mm256_unpackhi_ps(inI[2], inI[3]);
rI[4] = _mm256_unpacklo_ps(inI[4], inI[5]);
rI[5] = _mm256_unpackhi_ps(inI[4], inI[5]);
rI[6] = _mm256_unpacklo_ps(inI[6], inI[7]);
rI[7] = _mm256_unpackhi_ps(inI[6], inI[7]);
__m256 rrF[8];
__m256 *rF = reinterpret_cast<__m256 *>(rI);
rrF[0] = _mm256_shuffle_ps(rF[0], rF[2], _MM_SHUFFLE(1,0,1,0));
rrF[1] = _mm256_shuffle_ps(rF[0], rF[2], _MM_SHUFFLE(3,2,3,2));
rrF[2] = _mm256_shuffle_ps(rF[1], rF[3], _MM_SHUFFLE(1,0,1,0));
rrF[3] = _mm256_shuffle_ps(rF[1], rF[3], _MM_SHUFFLE(3,2,3,2));
rrF[4] = _mm256_shuffle_ps(rF[4], rF[6], _MM_SHUFFLE(1,0,1,0));
rrF[5] = _mm256_shuffle_ps(rF[4], rF[6], _MM_SHUFFLE(3,2,3,2));
rrF[6] = _mm256_shuffle_ps(rF[5], rF[7], _MM_SHUFFLE(1,0,1,0));
rrF[7] = _mm256_shuffle_ps(rF[5], rF[7], _MM_SHUFFLE(3,2,3,2));
rF = reinterpret_cast<__m256 *>(in);
rF[0] = _mm256_permute2f128_ps(rrF[0], rrF[4], 0x20);
rF[1] = _mm256_permute2f128_ps(rrF[1], rrF[5], 0x20);
rF[2] = _mm256_permute2f128_ps(rrF[2], rrF[6], 0x20);
rF[3] = _mm256_permute2f128_ps(rrF[3], rrF[7], 0x20);
rF[4] = _mm256_permute2f128_ps(rrF[0], rrF[4], 0x31);
rF[5] = _mm256_permute2f128_ps(rrF[1], rrF[5], 0x31);
rF[6] = _mm256_permute2f128_ps(rrF[2], rrF[6], 0x31);
rF[7] = _mm256_permute2f128_ps(rrF[3], rrF[7], 0x31);
}
inline void Transpose8x8Permute(unsigned long *in)
{
__m256i *inI = reinterpret_cast<__m256i *>(in);
__m256i rI[8];
rI[0] = _mm256_permute2f128_si256(inI[0], inI[4], 0x20);
rI[1] = _mm256_permute2f128_si256(inI[0], inI[4], 0x31);
rI[2] = _mm256_permute2f128_si256(inI[1], inI[5], 0x20);
rI[3] = _mm256_permute2f128_si256(inI[1], inI[5], 0x31);
rI[4] = _mm256_permute2f128_si256(inI[2], inI[6], 0x20);
rI[5] = _mm256_permute2f128_si256(inI[2], inI[6], 0x31);
rI[6] = _mm256_permute2f128_si256(inI[3], inI[7], 0x20);
rI[7] = _mm256_permute2f128_si256(inI[3], inI[7], 0x31);
__m256 rrF[8];
__m256 *rF = reinterpret_cast<__m256 *>(rI);
rrF[0] = _mm256_unpacklo_ps(rF[0], rF[4]);
rrF[1] = _mm256_unpackhi_ps(rF[0], rF[4]);
rrF[2] = _mm256_unpacklo_ps(rF[1], rF[5]);
rrF[3] = _mm256_unpackhi_ps(rF[1], rF[5]);
rrF[4] = _mm256_unpacklo_ps(rF[2], rF[6]);
rrF[5] = _mm256_unpackhi_ps(rF[2], rF[6]);
rrF[6] = _mm256_unpacklo_ps(rF[3], rF[7]);
rrF[7] = _mm256_unpackhi_ps(rF[3], rF[7]);
rF = reinterpret_cast<__m256 *>(in);
rF[0] = _mm256_unpacklo_ps(rrF[0], rrF[4]);
rF[1] = _mm256_unpackhi_ps(rrF[0], rrF[4]);
rF[2] = _mm256_unpacklo_ps(rrF[1], rrF[5]);
rF[3] = _mm256_unpackhi_ps(rrF[1], rrF[5]);
rF[4] = _mm256_unpacklo_ps(rrF[2], rrF[6]);
rF[5] = _mm256_unpackhi_ps(rrF[2], rrF[6]);
rF[6] = _mm256_unpacklo_ps(rrF[3], rrF[7]);
rF[7] = _mm256_unpackhi_ps(rrF[3], rrF[7]);
}
inline void Transpose8x8Insert(unsigned long *in)
{
__m256i *inIa = reinterpret_cast<__m256i *>(in);
__m256i *inIb = reinterpret_cast<__m256i *>(&reinterpret_cast<__m128i *>(in)[1]);
__m128i *inI128 = reinterpret_cast<__m128i *>(in);
__m256i rI[8];
rI[0] = _mm256_insertf128_si256(inIa[0], inI128[8], 1);
rI[1] = _mm256_insertf128_si256(inIb[0], inI128[9], 1);
rI[2] = _mm256_insertf128_si256(inIa[1], inI128[10], 1);
rI[3] = _mm256_insertf128_si256(inIb[1], inI128[11], 1);
rI[4] = _mm256_insertf128_si256(inIa[2], inI128[12], 1);
rI[5] = _mm256_insertf128_si256(inIb[2], inI128[13], 1);
rI[6] = _mm256_insertf128_si256(inIa[3], inI128[14], 1);
rI[7] = _mm256_insertf128_si256(inIb[3], inI128[15], 1);
__m256 rrF[8];
__m256 *rF = reinterpret_cast<__m256 *>(rI);
rrF[0] = _mm256_unpacklo_ps(rF[0], rF[4]);
rrF[1] = _mm256_unpackhi_ps(rF[0], rF[4]);
rrF[2] = _mm256_unpacklo_ps(rF[1], rF[5]);
rrF[3] = _mm256_unpackhi_ps(rF[1], rF[5]);
rrF[4] = _mm256_unpacklo_ps(rF[2], rF[6]);
rrF[5] = _mm256_unpackhi_ps(rF[2], rF[6]);
rrF[6] = _mm256_unpacklo_ps(rF[3], rF[7]);
rrF[7] = _mm256_unpackhi_ps(rF[3], rF[7]);
rF = reinterpret_cast<__m256 *>(in);
rF[0] = _mm256_unpacklo_ps(rrF[0], rrF[4]);
rF[1] = _mm256_unpackhi_ps(rrF[0], rrF[4]);
rF[2] = _mm256_unpacklo_ps(rrF[1], rrF[5]);
rF[3] = _mm256_unpackhi_ps(rrF[1], rrF[5]);
rF[4] = _mm256_unpacklo_ps(rrF[2], rrF[6]);
rF[5] = _mm256_unpackhi_ps(rrF[2], rrF[6]);
rF[6] = _mm256_unpacklo_ps(rrF[3], rrF[7]);
rF[7] = _mm256_unpackhi_ps(rrF[3], rrF[7]);
}
int main()
{
#define dwordCount 64
alignas(32) unsigned long mat[dwordCount];
for (int i = 0; i < dwordCount; i++) {
mat[i] = i;
}
clock_t t;
printf ("Transpose8x8Shuff 100,000,000 times\n");
Transpose8x8Shuff(mat);
t = clock();
int i = 100000000;
do {
Transpose8x8Shuff(mat);
} while (--i >= 0);
t = clock() - t;
volatile int dummy = mat[2];
printf ("(%f seconds).\n",((float)t)/CLOCKS_PER_SEC);
printf ("Transpose8x8Permute 100,000,000 times\n");
Transpose8x8Permute(mat);
t = clock();
i = 100000000;
do {
Transpose8x8Permute(mat);
} while (--i >= 0);
t = clock() - t;
volatile int dummy = mat[2];
printf ("(%f seconds).\n",((float)t)/CLOCKS_PER_SEC);
printf ("Transpose8x8Insert 100,000,000 times\n");
Transpose8x8Insert(mat);
t = clock();
i = 100000000;
do {
Transpose8x8Insert(mat);
} while (--i >= 0);
t = clock() - t;
volatile int dummy = mat[2];
printf ("(%f seconds).\n",((float)t)/CLOCKS_PER_SEC);
char c = getchar();
return 0;
}
这是基准延迟,而不是吞吐量(因为一个转置的输出是下一个转置的输入),但是无论如何它可能会成为洗牌吞吐量的瓶颈。
Skylake i7-6700k @ 3.9GHz上的代码经过修改后的结果(请参见on the Godbolt compiler explorer),修复了以下错误:
printf
之前,在定时区域之外clock()
最后,volatile dummy = in[2]
不会使所有转置都优化(gcc实际上会这样做)。alignas(32)
而不是__declspec
,并且不包含stdafx.h
。)我没有解决__m256i*
/ __m256*
不必要的混合问题,也没有检查gcc或clang是否导致更糟糕的代码生成。我也没有使用std::chrono
高频率时钟,因为clock()
足够精确,可以进行很多次重复。
g ++ 7.3 -O3 -march=native
在Arch Linux上:Z Boson的版本最快
Transpose8x8Shuff 100,000,000 times
(0.637479 seconds).
Transpose8x8Permute 100,000,000 times
(0.792658 seconds).
Transpose8x8Insert 100,000,000 times
(0.846590 seconds).
clang ++ 5.0.1 -O3 -march=native
:8x8Permute的优化速度甚至比gcc更快,但是8x8Insert却被可怕地悲观了。
Transpose8x8Shuff 100,000,000 times
(0.642185 seconds).
Transpose8x8Permute 100,000,000 times
(0.622157 seconds).
Transpose8x8Insert 100,000,000 times
(2.149958 seconds).
从源代码生成的asm指令与内在函数不完全匹配:特别是clang拥有一个shuffle优化器,它真正地编译,与优化+
之类的标量代码的方式相同整数。 Transpose8x8Insert
的速度不应该那么慢,所以c语肯定选错了。
答案 3 :(得分:1)
除了先前的答案以外,在这种情况下使用shuffle几乎是多余的,因为我们可以解包/解包来获得结果。 shuffle和unpack指令具有相同的延迟/吞吐量,但是shuffle会在机器码op中生成一个额外的字节(即,对于shuffle是5个字节,对于unpack是4个字节)。
在 some 点,我们将需要8个置换。这是一个较慢的操作(延迟为3个周期),因此我们希望在可能的情况下更早启动这些操作。假设内联transpose8f方法(应该这样做!),则应将a-> h args所需的所有负载合并到解压缩指令中。
您可能面临的唯一次要问题是,由于您在此处使用了8个以上的寄存器,因此可能会溢出到YMM9及更高版本中。这可能会导致VEX2 ops生成为VEX3,这将为每个op增加一个字节。
结果,随着一些微动,您将得到以下结果:
typedef __m256 f256;
#define unpacklo8f _mm256_unpacklo_ps
#define unpackhi8f _mm256_unpackhi_ps
template<uint8_t X, uint8_t Y>
inline f256 permute128f(const f256 a, const f256 b)
{
return _mm256_permute2f128_ps(a, b, X | (Y << 4));
}
inline void transpose8f(
const f256 a, const f256 b, const f256 c, const f256 d,
const f256 e, const f256 f, const f256 g, const f256 h,
f256& s, f256& t, f256& u, f256& v,
f256& x, f256& y, f256& z, f256& w)
{
const f256 t00 = unpacklo8f(a, c);
const f256 t02 = unpacklo8f(b, d);
const f256 t20 = unpacklo8f(e, g);
const f256 t22 = unpacklo8f(f, h);
const f256 t10 = unpacklo8f(t00, t02);
const f256 t30 = unpacklo8f(t20, t22);
const f256 t11 = unpackhi8f(t00, t02);
s = permute128f<0, 2>(t10, t30);
const f256 t31 = unpackhi8f(t20, t22);
x = permute128f<1, 3>(t10, t30);
const f256 t01 = unpackhi8f(a, c);
t = permute128f<0, 2>(t11, t31);
const f256 t21 = unpackhi8f(e, g);
y = permute128f<1, 3>(t11, t31);
const f256 t03 = unpackhi8f(b, d);
const f256 t23 = unpackhi8f(f, h);
const f256 t12 = unpacklo8f(t01, t03);
const f256 t13 = unpackhi8f(t01, t03);
const f256 t32 = unpacklo8f(t21, t23);
const f256 t33 = unpackhi8f(t21, t23);
u = permute128f<0, 2>(t12, t32);
z = permute128f<1, 3>(t12, t32);
v = permute128f<0, 2>(t13, t33);
w = permute128f<1, 3>(t13, t33);
}
您将不会对此有所改进(您可以先进行128位置换,然后解压缩,但最终它们将是相同的)。
答案 4 :(得分:0)
这是我的解决方案,使用的指令较少,性能非常好,快了大约8倍。我已经在Fedora中使用ICC,GCC和Clang进行了测试。
#include <stdio.h>
#include <x86intrin.h>
#define MAX1 128
#define MAX2 MAX1
float __attribute__(( aligned(32))) a_tra[MAX2][MAX1], __attribute__(( aligned(32))) a[MAX1][MAX2] ;
int main()
{
int i,j;//, ii=0,jj=0;
// variables for vector section
int vindexm [8]={0, MAX1, MAX1*2, MAX1*3, MAX1*4, MAX1*5, MAX1*6, MAX1*7 };
__m256i vindex = _mm256_load_si256((__m256i *) &vindexm[0]);
__m256 vec1, vec2, vec3, vec4, vec5, vec6, vec7, vec8;
for(i=0; i<MAX1; i+=8){
for(j=0; j<MAX2; j+=8){
//loading from columns
vec1 = _mm256_i32gather_ps (&a[i][j+0],vindex,4);
vec2 = _mm256_i32gather_ps (&a[i][j+1],vindex,4);
vec3 = _mm256_i32gather_ps (&a[i][j+2],vindex,4);
vec4 = _mm256_i32gather_ps (&a[i][j+3],vindex,4);
vec5 = _mm256_i32gather_ps (&a[i][j+4],vindex,4);
vec6 = _mm256_i32gather_ps (&a[i][j+5],vindex,4);
vec7 = _mm256_i32gather_ps (&a[i][j+6],vindex,4);
vec8 = _mm256_i32gather_ps (&a[i][j+7],vindex,4);
//storing to the rows
_mm256_store_ps(&a_tra[j+0][i], vec1);
_mm256_store_ps(&a_tra[j+1][i], vec2);
_mm256_store_ps(&a_tra[j+2][i], vec3);
_mm256_store_ps(&a_tra[j+3][i], vec4);
_mm256_store_ps(&a_tra[j+4][i], vec5);
_mm256_store_ps(&a_tra[j+5][i], vec6);
_mm256_store_ps(&a_tra[j+6][i], vec7);
_mm256_store_ps(&a_tra[j+7][i], vec8);
}
}
return 0;
}