如何减轻转置数组访问顺序对性能的影响?

时间:2015-06-11 15:12:31

标签: c performance memory multidimensional-array cpu-cache

我有一个用C编写的算法,它处理一对二维数组(例如大小为Y x X)以产生另一个相同大小的二维数组。所有三个数组都包含32位浮点数并且具有相同的大小,Y x X,其中Y可能是几十,但X是一百万左右。

不幸的是:

  • 所有数组必须位于row-major order(通过X扫描访问连续内存),
  • 算法要求最里面的循环扫描Y维度。

也许不出所料,以这种不连续的方式访问数据的速度相对较慢。所以......

我可以做些什么来减轻非连续内存访问对性能的影响?

(NB。这是一个long shot,但我已经尝试了各种预取指令模式来引入即将发布的列,但都无济于事。)

以下(更新)代码演示了此问题:

#include <stdio.h>
#include <stdlib.h>

#define NX 1000000
#define NY 30

int main() {
    float *a = malloc(sizeof(float) * NY * NX);
    float *b = malloc(sizeof(float) * NY * NX);
    float *c = malloc(sizeof(float) * NY * NX);

    size_t y, x, offset;
    float v;

    for(x=0; x<NX; x++) {
        v = 1;
        for(y=0; y<NY; y++) {
            offset = x + NX * y;
            if(a[offset] < 0) {
                v = 2;
            }
            c[offset] = v * b[offset];
        }
    }

    free(a);
    free(b);
    free(c);
}

在配备E5520 CPU @ 2.27 GHz的测试机器上,即使它仅读取~220 MB并写入~110 MB,执行也需要约1秒。

1 个答案:

答案 0 :(得分:2)

看起来您的访问模式不应该是有害的。这让我想知道branch prediction是否是你真正的问题。

通常转换数据访问是以块的形式完成的,以保持缓存健康,但是你的输入在内循环轴上是如此之短,以至于当你在外部重新访问它时,第一行的缓存读取仍然有效循环。

你有三个数组30个元素高,缓存行宽可能是128个字节(我希望更小,但事情会发生变化)。那个顶行只需要12kB的缓存来保持居住。

但您可以尝试将v更改为小数组并以垂直条纹进行处理。即使这实际上没有帮助您的缓存利用率,它至少会给编译器提示它可以使用SIMD进行优化。

你也可以尝试这种危险的优化来消除分支:

for(x=0; x<NX; x++) {
    uint32_t v = 0;
    for(y=0; y<NY; y++) {
        offset = x + NX * y;
        v |= (((uint32_t *)a)[offset] & 0x80000000) >> 8;
        ((uint32_t *)c)[offset] = ((uint32_t *)b)[offset] + v;
    }
}

这是日志域中的算术,取浮点值的符号位并将其直接添加到指数中,假设它不会溢出。假设内存中的格式为uint32_t - 兼容。