普通视图

发现新文章,点击刷新页面。
昨天以前Amicoyuan的高性能计算世界

Greyson Chance 2023 Beijing

2023年7月30日 23:03

Greyson Chance 2023 Beijing

总结

记得最早开始听,应该是在初中,从最初的No Fear到后来的最爱的Seasons。他19年来中国我是完全不知道,都是大学开班会,同学看到我头像加了我,我才知道,这次也是在她朋友圈看到了消息,哈哈哈哈,respect!这次终于赶上了!

哈哈哈哈哈哈,这次认识了好多新朋友,大家都好nice,白玫瑰小队下次又见!我甚至连之前一直在B站看的Seasons杭州场的MV的up都认识了,属于说非常巧了。

现场真的好震撼,导致我回去久久不能平复,GC真的行走的CD哈哈哈。我给他说能不能唱Seasons,他说抱歉,希望你能享受今晚。上大学后我其实很少关注GC了,但是Seasons总会在一年的那么些日子里单曲循环,新专辑更是听都没听过,4月后我就开始慢慢听着新专辑的歌,期待现场见了哈哈!

结束后我又想了想,初中,高中,大学,疫情,好像时间真的过得很快,转眼间我都毕业了。这次我真的感到好像当年自己青春的遗憾慢慢画上一个感叹号了!

愿我一直No Fear,

Move forword like the seasons!

全场视频

https://www.aliyundrive.com/s/ZCaugLSs8TJ

重启Life分类-Seasons

2023年7月28日 08:06

重启Life分类-Seasons

在听完GC北京场后,感触颇深,再次启动Life分类还是有必要哈哈哈!

今天写这篇博客好像也脱了很久。

【上次因为糟糕的排版删除了23年的厦门篇,Sorry,后期会补上】

同时恭喜队伍成功进入CPC2023决赛,这波是青岛见了,哈哈哈!手动撒花!

我发现有些瞬间还是必须照片或者文字记下来,不然后面真的会忘记。

立个FLAG今年在Life分类更新完23年的旅行以及这次的GC北京场。

后续的计划大概是,博客里面写文字内容,同时贴上云盘的视频和照片。因为这样就可以避免糟糕的排版了,主要还是对前端不太熟。

Packing into contiguous memory

2023年6月7日 21:47

Packing into contiguous memory

这将带来惊人的性能提升:

img

img

我们现在达到了处理器90%的涡轮增压峰值!

img

img

Optimization_4x4_12

在调用AddDot4x4之前,我们现在打包到4xk的A块。我们看到性能下降。如果检查内部内核,就会注意到每个4xk的A块都被重复打包,每次执行外部循环一次。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

/* Create macros so that the matrices are stored in column-major order */

#define A(i,j) a[ (j)*lda + (i) ]
#define B(i,j) b[ (j)*ldb + (i) ]
#define C(i,j) c[ (j)*ldc + (i) ]

/* Block sizes */
#define mc 256
#define kc 128

#define min( i, j ) ( (i)<(j) ? (i): (j) )

/* Routine for computing C = A * B + C */

void AddDot4x4( int, double *, int, double *, int, double *, int );
void PackMatrixA( int, double *, int, double * );

void MY_MMult( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc )
{
int i, p, pb, ib;

/* This time, we compute a mc x n block of C by a call to the InnerKernel */

for ( p=0; p<k; p+=kc ){
pb = min( k-p, kc );
for ( i=0; i<m; i+=mc ){
ib = min( m-i, mc );
InnerKernel( ib, n, pb, &A( i,p ), lda, &B(p, 0 ), ldb, &C( i,0 ), ldc );
}
}
}

void InnerKernel( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc )
{
int i, j;
double
packedA[ m * k ];

for ( j=0; j<n; j+=4 ){ /* Loop over the columns of C, unrolled by 4 */
for ( i=0; i<m; i+=4 ){ /* Loop over the rows of C */
/* Update C( i,j ), C( i,j+1 ), C( i,j+2 ), and C( i,j+3 ) in
one routine (four inner products) */
PackMatrixA( k, &A( i, 0 ), lda, &packedA[ i*k ] );
AddDot4x4( k, &packedA[ i*k ], 4, &B( 0,j ), ldb, &C( i,j ), ldc );
}
}
}

void PackMatrixA( int k, double *a, int lda, double *a_to )
{
int j;

for( j=0; j<k; j++){ /* loop over columns of A */
double
*a_ij_pntr = &A( 0, j );

*a_to++ = *a_ij_pntr;
*a_to++ = *(a_ij_pntr+1);
*a_to++ = *(a_ij_pntr+2);
*a_to++ = *(a_ij_pntr+3);
}
}

#include <mmintrin.h>
#include <xmmintrin.h> // SSE
#include <pmmintrin.h> // SSE2
#include <emmintrin.h> // SSE3

typedef union
{
__m128d v;
double d[2];
} v2df_t;

void AddDot4x4( int k, double *a, int lda, double *b, int ldb, double *c, int ldc )
{
/* So, this routine computes a 4x4 block of matrix A

C( 0, 0 ), C( 0, 1 ), C( 0, 2 ), C( 0, 3 ).
C( 1, 0 ), C( 1, 1 ), C( 1, 2 ), C( 1, 3 ).
C( 2, 0 ), C( 2, 1 ), C( 2, 2 ), C( 2, 3 ).
C( 3, 0 ), C( 3, 1 ), C( 3, 2 ), C( 3, 3 ).

Notice that this routine is called with c = C( i, j ) in the
previous routine, so these are actually the elements

C( i , j ), C( i , j+1 ), C( i , j+2 ), C( i , j+3 )
C( i+1, j ), C( i+1, j+1 ), C( i+1, j+2 ), C( i+1, j+3 )
C( i+2, j ), C( i+2, j+1 ), C( i+2, j+2 ), C( i+2, j+3 )
C( i+3, j ), C( i+3, j+1 ), C( i+3, j+2 ), C( i+3, j+3 )

in the original matrix C

And now we use vector registers and instructions */

int p;
v2df_t
c_00_c_10_vreg, c_01_c_11_vreg, c_02_c_12_vreg, c_03_c_13_vreg,
c_20_c_30_vreg, c_21_c_31_vreg, c_22_c_32_vreg, c_23_c_33_vreg,
a_0p_a_1p_vreg,
a_2p_a_3p_vreg,
b_p0_vreg, b_p1_vreg, b_p2_vreg, b_p3_vreg;

double
/* Point to the current elements in the four columns of B */
*b_p0_pntr, *b_p1_pntr, *b_p2_pntr, *b_p3_pntr;

b_p0_pntr = &B( 0, 0 );
b_p1_pntr = &B( 0, 1 );
b_p2_pntr = &B( 0, 2 );
b_p3_pntr = &B( 0, 3 );

c_00_c_10_vreg.v = _mm_setzero_pd();
c_01_c_11_vreg.v = _mm_setzero_pd();
c_02_c_12_vreg.v = _mm_setzero_pd();
c_03_c_13_vreg.v = _mm_setzero_pd();
c_20_c_30_vreg.v = _mm_setzero_pd();
c_21_c_31_vreg.v = _mm_setzero_pd();
c_22_c_32_vreg.v = _mm_setzero_pd();
c_23_c_33_vreg.v = _mm_setzero_pd();

for ( p=0; p<k; p++ ){
a_0p_a_1p_vreg.v = _mm_load_pd( (double *) &A( 0, p ) );
a_2p_a_3p_vreg.v = _mm_load_pd( (double *) &A( 2, p ) );

b_p0_vreg.v = _mm_loaddup_pd( (double *) b_p0_pntr++ ); /* load and duplicate */
b_p1_vreg.v = _mm_loaddup_pd( (double *) b_p1_pntr++ ); /* load and duplicate */
b_p2_vreg.v = _mm_loaddup_pd( (double *) b_p2_pntr++ ); /* load and duplicate */
b_p3_vreg.v = _mm_loaddup_pd( (double *) b_p3_pntr++ ); /* load and duplicate */

/* First row and second rows */
c_00_c_10_vreg.v += a_0p_a_1p_vreg.v * b_p0_vreg.v;
c_01_c_11_vreg.v += a_0p_a_1p_vreg.v * b_p1_vreg.v;
c_02_c_12_vreg.v += a_0p_a_1p_vreg.v * b_p2_vreg.v;
c_03_c_13_vreg.v += a_0p_a_1p_vreg.v * b_p3_vreg.v;

/* Third and fourth rows */
c_20_c_30_vreg.v += a_2p_a_3p_vreg.v * b_p0_vreg.v;
c_21_c_31_vreg.v += a_2p_a_3p_vreg.v * b_p1_vreg.v;
c_22_c_32_vreg.v += a_2p_a_3p_vreg.v * b_p2_vreg.v;
c_23_c_33_vreg.v += a_2p_a_3p_vreg.v * b_p3_vreg.v;
}

C( 0, 0 ) += c_00_c_10_vreg.d[0]; C( 0, 1 ) += c_01_c_11_vreg.d[0];
C( 0, 2 ) += c_02_c_12_vreg.d[0]; C( 0, 3 ) += c_03_c_13_vreg.d[0];

C( 1, 0 ) += c_00_c_10_vreg.d[1]; C( 1, 1 ) += c_01_c_11_vreg.d[1];
C( 1, 2 ) += c_02_c_12_vreg.d[1]; C( 1, 3 ) += c_03_c_13_vreg.d[1];

C( 2, 0 ) += c_20_c_30_vreg.d[0]; C( 2, 1 ) += c_21_c_31_vreg.d[0];
C( 2, 2 ) += c_22_c_32_vreg.d[0]; C( 2, 3 ) += c_23_c_33_vreg.d[0];

C( 3, 0 ) += c_20_c_30_vreg.d[1]; C( 3, 1 ) += c_21_c_31_vreg.d[1];
C( 3, 2 ) += c_22_c_32_vreg.d[1]; C( 3, 3 ) += c_23_c_33_vreg.d[1];
}

Optimization_4x4_13

这个版本保存了A的打包块,以便在InnerKernel的外部循环的第一次迭代之后,使用保存的版本。性能的提升是显而易见的!与上一个版本相比,唯一的变化是增加了if (j== 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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

/* Create macros so that the matrices are stored in column-major order */

#define A(i,j) a[ (j)*lda + (i) ]
#define B(i,j) b[ (j)*ldb + (i) ]
#define C(i,j) c[ (j)*ldc + (i) ]

/* Block sizes */
#define mc 256
#define kc 128

#define min( i, j ) ( (i)<(j) ? (i): (j) )

/* Routine for computing C = A * B + C */

void AddDot4x4( int, double *, int, double *, int, double *, int );
void PackMatrixA( int, double *, int, double * );

void MY_MMult( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc )
{
int i, p, pb, ib;

/* This time, we compute a mc x n block of C by a call to the InnerKernel */

for ( p=0; p<k; p+=kc ){
pb = min( k-p, kc );
for ( i=0; i<m; i+=mc ){
ib = min( m-i, mc );
InnerKernel( ib, n, pb, &A( i,p ), lda, &B(p, 0 ), ldb, &C( i,0 ), ldc );
}
}
}

void InnerKernel( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc )
{
int i, j;
double
packedA[ m * k ];

for ( j=0; j<n; j+=4 ){ /* Loop over the columns of C, unrolled by 4 */
for ( i=0; i<m; i+=4 ){ /* Loop over the rows of C */
/* Update C( i,j ), C( i,j+1 ), C( i,j+2 ), and C( i,j+3 ) in
one routine (four inner products) */
if ( j == 0 ) PackMatrixA( k, &A( i, 0 ), lda, &packedA[ i*k ] );
AddDot4x4( k, &packedA[ i*k ], 4, &B( 0,j ), ldb, &C( i,j ), ldc );
}
}
}

void PackMatrixA( int k, double *a, int lda, double *a_to )
{
int j;

for( j=0; j<k; j++){ /* loop over columns of A */
double
*a_ij_pntr = &A( 0, j );

*a_to++ = *a_ij_pntr;
*a_to++ = *(a_ij_pntr+1);
*a_to++ = *(a_ij_pntr+2);
*a_to++ = *(a_ij_pntr+3);
}
}

#include <mmintrin.h>
#include <xmmintrin.h> // SSE
#include <pmmintrin.h> // SSE2
#include <emmintrin.h> // SSE3

typedef union
{
__m128d v;
double d[2];
} v2df_t;

void AddDot4x4( int k, double *a, int lda, double *b, int ldb, double *c, int ldc )
{
/* So, this routine computes a 4x4 block of matrix A

C( 0, 0 ), C( 0, 1 ), C( 0, 2 ), C( 0, 3 ).
C( 1, 0 ), C( 1, 1 ), C( 1, 2 ), C( 1, 3 ).
C( 2, 0 ), C( 2, 1 ), C( 2, 2 ), C( 2, 3 ).
C( 3, 0 ), C( 3, 1 ), C( 3, 2 ), C( 3, 3 ).

Notice that this routine is called with c = C( i, j ) in the
previous routine, so these are actually the elements

C( i , j ), C( i , j+1 ), C( i , j+2 ), C( i , j+3 )
C( i+1, j ), C( i+1, j+1 ), C( i+1, j+2 ), C( i+1, j+3 )
C( i+2, j ), C( i+2, j+1 ), C( i+2, j+2 ), C( i+2, j+3 )
C( i+3, j ), C( i+3, j+1 ), C( i+3, j+2 ), C( i+3, j+3 )

in the original matrix C

And now we use vector registers and instructions */

int p;
v2df_t
c_00_c_10_vreg, c_01_c_11_vreg, c_02_c_12_vreg, c_03_c_13_vreg,
c_20_c_30_vreg, c_21_c_31_vreg, c_22_c_32_vreg, c_23_c_33_vreg,
a_0p_a_1p_vreg,
a_2p_a_3p_vreg,
b_p0_vreg, b_p1_vreg, b_p2_vreg, b_p3_vreg;

double
/* Point to the current elements in the four columns of B */
*b_p0_pntr, *b_p1_pntr, *b_p2_pntr, *b_p3_pntr;

b_p0_pntr = &B( 0, 0 );
b_p1_pntr = &B( 0, 1 );
b_p2_pntr = &B( 0, 2 );
b_p3_pntr = &B( 0, 3 );

c_00_c_10_vreg.v = _mm_setzero_pd();
c_01_c_11_vreg.v = _mm_setzero_pd();
c_02_c_12_vreg.v = _mm_setzero_pd();
c_03_c_13_vreg.v = _mm_setzero_pd();
c_20_c_30_vreg.v = _mm_setzero_pd();
c_21_c_31_vreg.v = _mm_setzero_pd();
c_22_c_32_vreg.v = _mm_setzero_pd();
c_23_c_33_vreg.v = _mm_setzero_pd();

for ( p=0; p<k; p++ ){
a_0p_a_1p_vreg.v = _mm_load_pd( (double *) a );
a_2p_a_3p_vreg.v = _mm_load_pd( (double *) ( a+2 ) );
a += 4;

b_p0_vreg.v = _mm_loaddup_pd( (double *) b_p0_pntr++ ); /* load and duplicate */
b_p1_vreg.v = _mm_loaddup_pd( (double *) b_p1_pntr++ ); /* load and duplicate */
b_p2_vreg.v = _mm_loaddup_pd( (double *) b_p2_pntr++ ); /* load and duplicate */
b_p3_vreg.v = _mm_loaddup_pd( (double *) b_p3_pntr++ ); /* load and duplicate */

/* First row and second rows */
c_00_c_10_vreg.v += a_0p_a_1p_vreg.v * b_p0_vreg.v;
c_01_c_11_vreg.v += a_0p_a_1p_vreg.v * b_p1_vreg.v;
c_02_c_12_vreg.v += a_0p_a_1p_vreg.v * b_p2_vreg.v;
c_03_c_13_vreg.v += a_0p_a_1p_vreg.v * b_p3_vreg.v;

/* Third and fourth rows */
c_20_c_30_vreg.v += a_2p_a_3p_vreg.v * b_p0_vreg.v;
c_21_c_31_vreg.v += a_2p_a_3p_vreg.v * b_p1_vreg.v;
c_22_c_32_vreg.v += a_2p_a_3p_vreg.v * b_p2_vreg.v;
c_23_c_33_vreg.v += a_2p_a_3p_vreg.v * b_p3_vreg.v;
}

C( 0, 0 ) += c_00_c_10_vreg.d[0]; C( 0, 1 ) += c_01_c_11_vreg.d[0];
C( 0, 2 ) += c_02_c_12_vreg.d[0]; C( 0, 3 ) += c_03_c_13_vreg.d[0];

C( 1, 0 ) += c_00_c_10_vreg.d[1]; C( 1, 1 ) += c_01_c_11_vreg.d[1];
C( 1, 2 ) += c_02_c_12_vreg.d[1]; C( 1, 3 ) += c_03_c_13_vreg.d[1];

C( 2, 0 ) += c_20_c_30_vreg.d[0]; C( 2, 1 ) += c_21_c_31_vreg.d[0];
C( 2, 2 ) += c_22_c_32_vreg.d[0]; C( 2, 3 ) += c_23_c_33_vreg.d[0];

C( 3, 0 ) += c_20_c_30_vreg.d[1]; C( 3, 1 ) += c_21_c_31_vreg.d[1];
C( 3, 2 ) += c_22_c_32_vreg.d[1]; C( 3, 3 ) += c_23_c_33_vreg.d[1];
}

Optimization_4x4_14

我们现在打包b的kx4块,注意,在这个版本中,面板是重复打包的,这会对性能产生不利影响。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/* Create macros so that the matrices are stored in column-major order */

#define A(i,j) a[ (j)*lda + (i) ]
#define B(i,j) b[ (j)*ldb + (i) ]
#define C(i,j) c[ (j)*ldc + (i) ]

/* Block sizes */
#define mc 256
#define kc 128

#define min( i, j ) ( (i)<(j) ? (i): (j) )

/* Routine for computing C = A * B + C */

void AddDot4x4( int, double *, int, double *, int, double *, int );
void PackMatrixA( int, double *, int, double * );
void PackMatrixB( int, double *, int, double * );
void InnerKernel( int, int, int, double *, int, double *, int, double *, int, int );

void MY_MMult( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc )
{
int i, p, pb, ib;

/* This time, we compute a mc x n block of C by a call to the InnerKernel */

for ( p=0; p<k; p+=kc ){
pb = min( k-p, kc );
for ( i=0; i<m; i+=mc ){
ib = min( m-i, mc );
InnerKernel( ib, n, pb, &A( i,p ), lda, &B(p, 0 ), ldb, &C( i,0 ), ldc, i==0 );
}
}
}

void InnerKernel( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc, int first_time )
{
int i, j;
double
packedA[ m * k ], packedB[ k*n ];

for ( j=0; j<n; j+=4 ){ /* Loop over the columns of C, unrolled by 4 */
PackMatrixB( k, &B( 0, j ), ldb, &packedB[ j*k ] );
for ( i=0; i<m; i+=4 ){ /* Loop over the rows of C */
/* Update C( i,j ), C( i,j+1 ), C( i,j+2 ), and C( i,j+3 ) in
one routine (four inner products) */
if ( j == 0 )
PackMatrixA( k, &A( i, 0 ), lda, &packedA[ i*k ] );
AddDot4x4( k, &packedA[ i*k ], 4, &packedB[ j*k ], k, &C( i,j ), ldc );
}
}
}

void PackMatrixA( int k, double *a, int lda, double *a_to )
{
int j;

for( j=0; j<k; j++){ /* loop over columns of A */
double
*a_ij_pntr = &A( 0, j );

*a_to = *a_ij_pntr;
*(a_to+1) = *(a_ij_pntr+1);
*(a_to+2) = *(a_ij_pntr+2);
*(a_to+3) = *(a_ij_pntr+3);

a_to += 4;
}
}

void PackMatrixB( int k, double *b, int ldb, double *b_to )
{
int i;
double
*b_i0_pntr = &B( 0, 0 ), *b_i1_pntr = &B( 0, 1 ),
*b_i2_pntr = &B( 0, 2 ), *b_i3_pntr = &B( 0, 3 );

for( i=0; i<k; i++){ /* loop over rows of B */
*b_to++ = *b_i0_pntr++;
*b_to++ = *b_i1_pntr++;
*b_to++ = *b_i2_pntr++;
*b_to++ = *b_i3_pntr++;
}
}

#include <mmintrin.h>
#include <xmmintrin.h> // SSE
#include <pmmintrin.h> // SSE2
#include <emmintrin.h> // SSE3

typedef union
{
__m128d v;
double d[2];
} v2df_t;

void AddDot4x4( int k, double *a, int lda, double *b, int ldb, double *c, int ldc )
{
/* So, this routine computes a 4x4 block of matrix A

C( 0, 0 ), C( 0, 1 ), C( 0, 2 ), C( 0, 3 ).
C( 1, 0 ), C( 1, 1 ), C( 1, 2 ), C( 1, 3 ).
C( 2, 0 ), C( 2, 1 ), C( 2, 2 ), C( 2, 3 ).
C( 3, 0 ), C( 3, 1 ), C( 3, 2 ), C( 3, 3 ).

Notice that this routine is called with c = C( i, j ) in the
previous routine, so these are actually the elements

C( i , j ), C( i , j+1 ), C( i , j+2 ), C( i , j+3 )
C( i+1, j ), C( i+1, j+1 ), C( i+1, j+2 ), C( i+1, j+3 )
C( i+2, j ), C( i+2, j+1 ), C( i+2, j+2 ), C( i+2, j+3 )
C( i+3, j ), C( i+3, j+1 ), C( i+3, j+2 ), C( i+3, j+3 )

in the original matrix C

And now we use vector registers and instructions */

int p;
v2df_t
c_00_c_10_vreg, c_01_c_11_vreg, c_02_c_12_vreg, c_03_c_13_vreg,
c_20_c_30_vreg, c_21_c_31_vreg, c_22_c_32_vreg, c_23_c_33_vreg,
a_0p_a_1p_vreg,
a_2p_a_3p_vreg,
b_p0_vreg, b_p1_vreg, b_p2_vreg, b_p3_vreg;

c_00_c_10_vreg.v = _mm_setzero_pd();
c_01_c_11_vreg.v = _mm_setzero_pd();
c_02_c_12_vreg.v = _mm_setzero_pd();
c_03_c_13_vreg.v = _mm_setzero_pd();
c_20_c_30_vreg.v = _mm_setzero_pd();
c_21_c_31_vreg.v = _mm_setzero_pd();
c_22_c_32_vreg.v = _mm_setzero_pd();
c_23_c_33_vreg.v = _mm_setzero_pd();

for ( p=0; p<k; p++ ){
a_0p_a_1p_vreg.v = _mm_load_pd( (double *) a );
a_2p_a_3p_vreg.v = _mm_load_pd( (double *) ( a+2 ) );
a += 4;

b_p0_vreg.v = _mm_loaddup_pd( (double *) b ); /* load and duplicate */
b_p1_vreg.v = _mm_loaddup_pd( (double *) (b+1) ); /* load and duplicate */
b_p2_vreg.v = _mm_loaddup_pd( (double *) (b+2) ); /* load and duplicate */
b_p3_vreg.v = _mm_loaddup_pd( (double *) (b+3) ); /* load and duplicate */

b += 4;

/* First row and second rows */
c_00_c_10_vreg.v += a_0p_a_1p_vreg.v * b_p0_vreg.v;
c_01_c_11_vreg.v += a_0p_a_1p_vreg.v * b_p1_vreg.v;
c_02_c_12_vreg.v += a_0p_a_1p_vreg.v * b_p2_vreg.v;
c_03_c_13_vreg.v += a_0p_a_1p_vreg.v * b_p3_vreg.v;

/* Third and fourth rows */
c_20_c_30_vreg.v += a_2p_a_3p_vreg.v * b_p0_vreg.v;
c_21_c_31_vreg.v += a_2p_a_3p_vreg.v * b_p1_vreg.v;
c_22_c_32_vreg.v += a_2p_a_3p_vreg.v * b_p2_vreg.v;
c_23_c_33_vreg.v += a_2p_a_3p_vreg.v * b_p3_vreg.v;
}

C( 0, 0 ) += c_00_c_10_vreg.d[0]; C( 0, 1 ) += c_01_c_11_vreg.d[0];
C( 0, 2 ) += c_02_c_12_vreg.d[0]; C( 0, 3 ) += c_03_c_13_vreg.d[0];

C( 1, 0 ) += c_00_c_10_vreg.d[1]; C( 1, 1 ) += c_01_c_11_vreg.d[1];
C( 1, 2 ) += c_02_c_12_vreg.d[1]; C( 1, 3 ) += c_03_c_13_vreg.d[1];

C( 2, 0 ) += c_20_c_30_vreg.d[0]; C( 2, 1 ) += c_21_c_31_vreg.d[0];
C( 2, 2 ) += c_22_c_32_vreg.d[0]; C( 2, 3 ) += c_23_c_33_vreg.d[0];

C( 3, 0 ) += c_20_c_30_vreg.d[1]; C( 3, 1 ) += c_21_c_31_vreg.d[1];
C( 3, 2 ) += c_22_c_32_vreg.d[1]; C( 3, 3 ) += c_23_c_33_vreg.d[1];
}

Optimization_4x4_15

并且,我们再次添加了一些代码,这样我们就可以避免重新打包b的kx4块。现在性能令人印象深刻!

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/* Create macros so that the matrices are stored in column-major order */

#define A(i,j) a[ (j)*lda + (i) ]
#define B(i,j) b[ (j)*ldb + (i) ]
#define C(i,j) c[ (j)*ldc + (i) ]

/* Block sizes */
#define mc 256
#define kc 128
#define nb 1000

#define min( i, j ) ( (i)<(j) ? (i): (j) )

/* Routine for computing C = A * B + C */

void AddDot4x4( int, double *, int, double *, int, double *, int );
void PackMatrixA( int, double *, int, double * );
void PackMatrixB( int, double *, int, double * );
void InnerKernel( int, int, int, double *, int, double *, int, double *, int, int );

void MY_MMult( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc )
{
int i, p, pb, ib;

/* This time, we compute a mc x n block of C by a call to the InnerKernel */

for ( p=0; p<k; p+=kc ){
pb = min( k-p, kc );
for ( i=0; i<m; i+=mc ){
ib = min( m-i, mc );
InnerKernel( ib, n, pb, &A( i,p ), lda, &B(p, 0 ), ldb, &C( i,0 ), ldc, i==0 );
}
}
}

void InnerKernel( int m, int n, int k, double *a, int lda,
double *b, int ldb,
double *c, int ldc, int first_time )
{
int i, j;
double
packedA[ m * k ];
static double
packedB[ kc*nb ]; /* Note: using a static buffer is not thread safe... */

for ( j=0; j<n; j+=4 ){ /* Loop over the columns of C, unrolled by 4 */
if ( first_time )
PackMatrixB( k, &B( 0, j ), ldb, &packedB[ j*k ] );
for ( i=0; i<m; i+=4 ){ /* Loop over the rows of C */
/* Update C( i,j ), C( i,j+1 ), C( i,j+2 ), and C( i,j+3 ) in
one routine (four inner products) */
if ( j == 0 )
PackMatrixA( k, &A( i, 0 ), lda, &packedA[ i*k ] );
AddDot4x4( k, &packedA[ i*k ], 4, &packedB[ j*k ], k, &C( i,j ), ldc );
}
}
}

void PackMatrixA( int k, double *a, int lda, double *a_to )
{
int j;

for( j=0; j<k; j++){ /* loop over columns of A */
double
*a_ij_pntr = &A( 0, j );

*a_to = *a_ij_pntr;
*(a_to+1) = *(a_ij_pntr+1);
*(a_to+2) = *(a_ij_pntr+2);
*(a_to+3) = *(a_ij_pntr+3);

a_to += 4;
}
}

void PackMatrixB( int k, double *b, int ldb, double *b_to )
{
int i;
double
*b_i0_pntr = &B( 0, 0 ), *b_i1_pntr = &B( 0, 1 ),
*b_i2_pntr = &B( 0, 2 ), *b_i3_pntr = &B( 0, 3 );

for( i=0; i<k; i++){ /* loop over rows of B */
*b_to++ = *b_i0_pntr++;
*b_to++ = *b_i1_pntr++;
*b_to++ = *b_i2_pntr++;
*b_to++ = *b_i3_pntr++;
}
}

#include <mmintrin.h>
#include <xmmintrin.h> // SSE
#include <pmmintrin.h> // SSE2
#include <emmintrin.h> // SSE3

typedef union
{
__m128d v;
double d[2];
} v2df_t;

void AddDot4x4( int k, double *a, int lda, double *b, int ldb, double *c, int ldc )
{
/* So, this routine computes a 4x4 block of matrix A

C( 0, 0 ), C( 0, 1 ), C( 0, 2 ), C( 0, 3 ).
C( 1, 0 ), C( 1, 1 ), C( 1, 2 ), C( 1, 3 ).
C( 2, 0 ), C( 2, 1 ), C( 2, 2 ), C( 2, 3 ).
C( 3, 0 ), C( 3, 1 ), C( 3, 2 ), C( 3, 3 ).

Notice that this routine is called with c = C( i, j ) in the
previous routine, so these are actually the elements

C( i , j ), C( i , j+1 ), C( i , j+2 ), C( i , j+3 )
C( i+1, j ), C( i+1, j+1 ), C( i+1, j+2 ), C( i+1, j+3 )
C( i+2, j ), C( i+2, j+1 ), C( i+2, j+2 ), C( i+2, j+3 )
C( i+3, j ), C( i+3, j+1 ), C( i+3, j+2 ), C( i+3, j+3 )

in the original matrix C

And now we use vector registers and instructions */

int p;
v2df_t
c_00_c_10_vreg, c_01_c_11_vreg, c_02_c_12_vreg, c_03_c_13_vreg,
c_20_c_30_vreg, c_21_c_31_vreg, c_22_c_32_vreg, c_23_c_33_vreg,
a_0p_a_1p_vreg,
a_2p_a_3p_vreg,
b_p0_vreg, b_p1_vreg, b_p2_vreg, b_p3_vreg;

c_00_c_10_vreg.v = _mm_setzero_pd();
c_01_c_11_vreg.v = _mm_setzero_pd();
c_02_c_12_vreg.v = _mm_setzero_pd();
c_03_c_13_vreg.v = _mm_setzero_pd();
c_20_c_30_vreg.v = _mm_setzero_pd();
c_21_c_31_vreg.v = _mm_setzero_pd();
c_22_c_32_vreg.v = _mm_setzero_pd();
c_23_c_33_vreg.v = _mm_setzero_pd();

for ( p=0; p<k; p++ ){
a_0p_a_1p_vreg.v = _mm_load_pd( (double *) a );
a_2p_a_3p_vreg.v = _mm_load_pd( (double *) ( a+2 ) );
a += 4;

b_p0_vreg.v = _mm_loaddup_pd( (double *) b ); /* load and duplicate */
b_p1_vreg.v = _mm_loaddup_pd( (double *) (b+1) ); /* load and duplicate */
b_p2_vreg.v = _mm_loaddup_pd( (double *) (b+2) ); /* load and duplicate */
b_p3_vreg.v = _mm_loaddup_pd( (double *) (b+3) ); /* load and duplicate */

b += 4;

/* First row and second rows */
c_00_c_10_vreg.v += a_0p_a_1p_vreg.v * b_p0_vreg.v;
c_01_c_11_vreg.v += a_0p_a_1p_vreg.v * b_p1_vreg.v;
c_02_c_12_vreg.v += a_0p_a_1p_vreg.v * b_p2_vreg.v;
c_03_c_13_vreg.v += a_0p_a_1p_vreg.v * b_p3_vreg.v;

/* Third and fourth rows */
c_20_c_30_vreg.v += a_2p_a_3p_vreg.v * b_p0_vreg.v;
c_21_c_31_vreg.v += a_2p_a_3p_vreg.v * b_p1_vreg.v;
c_22_c_32_vreg.v += a_2p_a_3p_vreg.v * b_p2_vreg.v;
c_23_c_33_vreg.v += a_2p_a_3p_vreg.v * b_p3_vreg.v;
}

C( 0, 0 ) += c_00_c_10_vreg.d[0]; C( 0, 1 ) += c_01_c_11_vreg.d[0];
C( 0, 2 ) += c_02_c_12_vreg.d[0]; C( 0, 3 ) += c_03_c_13_vreg.d[0];

C( 1, 0 ) += c_00_c_10_vreg.d[1]; C( 1, 1 ) += c_01_c_11_vreg.d[1];
C( 1, 2 ) += c_02_c_12_vreg.d[1]; C( 1, 3 ) += c_03_c_13_vreg.d[1];

C( 2, 0 ) += c_20_c_30_vreg.d[0]; C( 2, 1 ) += c_21_c_31_vreg.d[0];
C( 2, 2 ) += c_22_c_32_vreg.d[0]; C( 2, 3 ) += c_23_c_33_vreg.d[0];

C( 3, 0 ) += c_20_c_30_vreg.d[1]; C( 3, 1 ) += c_21_c_31_vreg.d[1];
C( 3, 2 ) += c_22_c_32_vreg.d[1]; C( 3, 3 ) += c_23_c_33_vreg.d[1];
}
❌
❌