我有一个用C编写的算法,它处理一对二维数组(例如大小为Y x X)以产生另一个相同大小的二维数组。所有三个数组都包含32位浮点数并且具有相同的大小,Y x X,其中Y可能是几十,但X是一百万左右。
不幸的是:
也许不出所料,以这种不连续的方式访问数据的速度相对较慢。所以......
我可以做些什么来减轻非连续内存访问对性能的影响?
(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秒。
答案 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
- 兼容。