Packing into contiguous memory

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];
}