【发布时间】:2020-08-13 20:05:24
【问题描述】:
我需要编写一个程序,将图像读入数组,执行卷积操作(锐化)并将其保存回文件(ppm)。我写了一个标准算法:
unsigned char* imgBefore = malloc(height*(3*width)*sizeof(unsigned char));
assert(fread(imgBefore, 3*width, height, inputFile) == height);
unsigned char* imgAfter = malloc(height*(3*width)*sizeof(unsigned char));
short ker[3][3] = {{0, -1, 0}, {-1, 5, -1}, {0, -1, 0}};
unsigned char r, g, b;
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
if(y == 0 || y == height-1 || x == 0 || x == width-1) {
r = imgBefore[3*(width*y + x) + 0];
g = imgBefore[3*(width*y + x) + 1];
b = imgBefore[3*(width*y + x) + 2];
imgAfter[3*(width*y + x) + 0] = r;
imgAfter[3*(width*y + x) + 1] = g;
imgAfter[3*(width*y + x) + 2] = b;
continue;
}
int rSum = 0, gSum = 0, bSum = 0, val;
for(int dy = 0; dy < 3; dy++) { // kernels height
int yy = 3*width*(y+dy-1);
for(int dx = 0; dx < 3; dx++) { // kerenels width
int xx = 3*(x+dx-1);
val = ker[dy][dx];
rSum += val * imgBefore[yy + xx + 0];
gSum += val * imgBefore[yy + xx + 1];
bSum += val * imgBefore[yy + xx + 2];
}
}
rSum = rSum < 0 ? 0 : (rSum > 255 ? 255 : rSum);
gSum = gSum < 0 ? 0 : (gSum > 255 ? 255 : gSum);
bSum = bSum < 0 ? 0 : (bSum > 255 ? 255 : bSum);
imgAfter[3*(width*y + x) + 0] = rSum;
imgAfter[3*(width*y + x) + 1] = gSum;
imgAfter[3*(width*y + x) + 2] = bSum;
}
}
fwrite(imgAfter, 3*width, height, outputFile);
接下来,我需要优化它与缓存交互的效率。在我看来,问题部分出在这段代码中:
for(int dy = 0; dy < 3; dy++) { // kernels height
int yy = 3*width*(y+dy-1);
for(int dx = 0; dx < 3; dx++) { // kerenels width
int xx = 3*(x+dx-1);
val = ker[dy][dx];
rSum += val * imgBefore[yy + xx + 0];
gSum += val * imgBefore[yy + xx + 1];
bSum += val * imgBefore[yy + xx + 2];
}
}
因为它首先加载矩阵的一行,仅使用 3 (9) 个元素,然后转到下一行。与缓存相比,这似乎完全无效。
我能做些什么来解决这个问题?
我还尝试重用单个像素或整行。所有这一切只会使结果恶化(很有可能我只是简单地实现了 FIFO 之类的结构,或者在错误的地方添加和读取它们)。如果一个程序需要它,它应该是什么样子? 对于评估,我使用: valgrind --tool=cachegrind --I1=32768,8,64 --D1=32768,8,64 --LL=1048576,16,64 ./a.out
如果有任何建议,我将不胜感激
【问题讨论】:
-
您假设代码受内存层次结构的限制,但我认为这不是最关键的一点:您的代码既没有被 GCC 也没有 Clang 向量化(它似乎也没有被并行化)。问题来自对 SIMD 不友好的数据布局。因此,我希望代码受到计算的限制。您是否尝试过分析您的程序(valgrind 除外)?您用于进行测试的图像的典型尺寸是多少?
-
这项工作的本质是优化缓存访问。我首先想到的事情之一是并行化,但我意识到可以使用编译器选项添加它,这在我的情况下是固定的。还是我弄错了?在编译选项中,唯一我不知道也找不到信息的是 -mssse3 选项。这能以某种方式帮助吗?图像分辨率 4608x3456
-
好的。
-mssse3是启用 SSSE3 指令集启用矢量化的选项。它在这里应该没有帮助,因为编译器可能无法自动 向量化此代码。对于并行化,您可以使用 OpenMP 轻松完成。在y循环之前的一个简单的#pragma omp parallel for指令应该会给你一个很好的加速(不要忘记使用编译器选项启用 OpenMP,比如 GCC/Clang 上的-fopenmp)。
标签: c caching optimization convolution ppm