读/比其宽度更大的步幅写基质导致性能的一大损失(reading/writing a matrix w

2019-10-19 07:54发布

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

Answer 1:

我认为问题出在最内层的循环,与预取做。 您在执行以下操作:

  1. 从C加载256个连续的字节(4高速缓存行)
  2. 从B 64次负载的256个连续的字节(总共256个高速缓存行)
  3. 保存连续256个字节到c

在步骤2中,当步幅是64,您正在有效地读取存储器长的连续的块。 当步幅是1024,你正在阅读的内存在不连续的步骤,一次256个字节。

其结果是,当步幅为64,预取读取连续的块为提前缓存,你会得到每行最多一个高速缓存未命中。 当步幅为1024,预取是由不连续混淆读取(你备用读取连续的高速缓存行和广泛分离的高速缓存行之间),每行有64个高速缓存未命中。



Answer 2:

我想我可能已经找到我的答案。 我认为这是relatted到翻译后备缓冲器(TLB)。

在本文高性能矩阵乘法的解剖由后藤和van de Geijn他们写

高速缓存未命中和TLB失败之间最显著的区别在于缓存未命中并不一定暂停CPU ... TLB缺失,相比之下,使CPU来搪塞,直到TLB已经与新地址进行更新。 换句话说,预取可以掩盖缓存未命中,但不是一个TLB失误“。

不久之后,在4.2.3节标题为“包装”他们写

现在的基本问题是,A是通常一个更大的矩阵的子矩阵,并且因此不在存储器中连续。 这又意味着,解决它需要比TLB项的数量降到最低等等。 解决的办法是组A中的连续工作阵列



文章来源: reading/writing a matrix with a stride much larger than its width causes a big loss in performance