我在1024x1024的矩阵做密集的矩阵乘法。 我用64×64瓦这个使用循环阻塞/平铺。 我创建了一个高度优化的64×64的矩阵乘法函数(见我的问题,为代码末尾)。
gemm64(float *a, float *b, float *c, int stride).
这里是一个运行在瓷砖的代码。 甲1024x1204矩阵,其具有16×16块。
for(int i=0; i<16; i++) {
for(int j=0; j<16; j++) {
for(int k=0; k<16; k++) {
gemm64(&a[64*(i*1024 + k)], &b[64*(k*1024 + j)], &c[64*(i*1024 + j)], 1024);
}
}
}
然而,作为一个猜测,我试图重新排列所有的瓦片的所述存储器(见这个问题的代码的末尾) b
在一个新的矩阵矩阵b2
,使得每个瓦片的步幅是64,而不是1024这有效地使得与跨距= 64×64 64的矩阵阵列。
float *b2 = (float*)_mm_malloc(1024*1024*sizeof(float), 64);
reorder(b, b2, 1024);
for(int i=0; i<16; i++) {
for(int j=0; j<16; j++) {
for(int k=0; k<16; k++) {
gemm64_v2(&a[64*(i*1024 + k)], &b2[64*64*(k*16 + j)], &c[64*(i*1024 + j)], 64);
}
}
}
_mm_free(b2);
注意如何对于b偏移改变&b[64*(k*1024 + j)]
至&b2[64*64*(k*16 + j)]
和传递给步幅gemm64
已经改变从1024到64。
我的代码的性能由小于20%一下子跳到我的Sandy Bridge系统上峰触发器的70%!
为什么重新排列的瓷砖,在这样的矩阵B做出如此巨大的差异?
阵列A,B,b2和c是64字节对齐的。
extern "C" void gemm64(float *a, float*b, float*c, int stride) {
for(int i=0; i<64; i++) {
row_m64x64(&a[1024*i], b, &c[1024*i], stride);
}
}
void row_m64x64(const float *a, const float *b, float *c, int stride) {
__m256 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
tmp0 = _mm256_loadu_ps(&c[ 0]);
tmp1 = _mm256_loadu_ps(&c[ 8]);
tmp2 = _mm256_loadu_ps(&c[16]);
tmp3 = _mm256_loadu_ps(&c[24]);
tmp4 = _mm256_loadu_ps(&c[32]);
tmp5 = _mm256_loadu_ps(&c[40]);
tmp6 = _mm256_loadu_ps(&c[48]);
tmp7 = _mm256_loadu_ps(&c[56]);
for(int i=0; i<64; i++) {
__m256 areg0 = _mm256_broadcast_ss(&a[i]);
__m256 breg0 = _mm256_loadu_ps(&b[stride*i + 0]);
tmp0 = _mm256_add_ps(_mm256_mul_ps(areg0,breg0), tmp0);
__m256 breg1 = _mm256_loadu_ps(&b[stride*i + 8]);
tmp1 = _mm256_add_ps(_mm256_mul_ps(areg0,breg1), tmp1);
__m256 breg2 = _mm256_loadu_ps(&b[stride*i + 16]);
tmp2 = _mm256_add_ps(_mm256_mul_ps(areg0,breg2), tmp2);
__m256 breg3 = _mm256_loadu_ps(&b[stride*i + 24]);
tmp3 = _mm256_add_ps(_mm256_mul_ps(areg0,breg3), tmp3);
__m256 breg4 = _mm256_loadu_ps(&b[stride*i + 32]);
tmp4 = _mm256_add_ps(_mm256_mul_ps(areg0,breg4), tmp4);
__m256 breg5 = _mm256_loadu_ps(&b[stride*i + 40]);
tmp5 = _mm256_add_ps(_mm256_mul_ps(areg0,breg5), tmp5);
__m256 breg6 = _mm256_loadu_ps(&b[stride*i + 48]);
tmp6 = _mm256_add_ps(_mm256_mul_ps(areg0,breg6), tmp6);
__m256 breg7 = _mm256_loadu_ps(&b[stride*i + 56]);
tmp7 = _mm256_add_ps(_mm256_mul_ps(areg0,breg7), tmp7);
}
_mm256_storeu_ps(&c[ 0], tmp0);
_mm256_storeu_ps(&c[ 8], tmp1);
_mm256_storeu_ps(&c[16], tmp2);
_mm256_storeu_ps(&c[24], tmp3);
_mm256_storeu_ps(&c[32], tmp4);
_mm256_storeu_ps(&c[40], tmp5);
_mm256_storeu_ps(&c[48], tmp6);
_mm256_storeu_ps(&c[56], tmp7);
}
代码重新排序的B矩阵。
reorder(float *b, float *b2, int stride) {
//int k = 0;
for(int i=0; i<16; i++) {
for(int j=0; j<16; j++) {
for(int i2=0; i2<64; i2++) {
for(int j2=0; j2<64; j2++) {
//b2[k++] = b[1024*(64*i+i2) + 64*j + j2];
b2[64*64*(i*16 + j) + 64*i2+j2] = b[1024*(64*i+i2) + 64*j + j2];
}
}
}
}
}