【发布时间】:2020-06-26 21:30:09
【问题描述】:
对于我的项目,我编写了直接 3D 卷积的简单 C 实现,并在输入上定期填充。不幸的是,由于我是 C 新手,所以性能不是很好……这是代码:
int mod(int a, int b)
{
// calculate mod to get the correct index with periodic padding
int r = a % b;
return r < 0 ? r + b : r;
}
void convolve3D(const double *image, const double *kernel, const int imageDimX, const int imageDimY, const int imageDimZ, const int stencilDimX, const int stencilDimY, const int stencilDimZ, double *result)
{
int imageSize = imageDimX * imageDimY * imageDimZ;
int kernelSize = kernelDimX * kernelDimY * kernelDimZ;
int i, j, k, l, m, n;
int kernelCenterX = (kernelDimX - 1) / 2;
int kernelCenterY = (kernelDimY - 1) / 2;
int kernelCenterZ = (kernelDimZ - 1) / 2;
int xShift,yShift,zShift;
int outIndex, outI, outJ, outK;
int imageIndex = 0, kernelIndex = 0;
// Loop through each voxel
for (k = 0; k < imageDimZ; k++){
for ( j = 0; j < imageDimY; j++) {
for ( i = 0; i < imageDimX; i++) {
stencilIndex = 0;
// for each voxel, loop through each kernel coefficient
for (n = 0; n < kernelDimZ; n++){
for ( m = 0; m < kernelDimY; m++) {
for ( l = 0; l < kernelDimX; l++) {
// find the index of the corresponding voxel in the output image
xShift = l - kernelCenterX;
yShift = m - kernelCenterY;
zShift = n - kernelCenterZ;
outI = mod ((i - xShift), imageDimX);
outJ = mod ((j - yShift), imageDimY);
outK = mod ((k - zShift), imageDimZ);
outIndex = outK * imageDimX * imageDimY + outJ * imageDimX + outI;
// calculate and add
result[outIndex] += stencil[stencilIndex]* image[imageIndex];
stencilIndex++;
}
}
}
imageIndex ++;
}
}
}
}
- 按照惯例,所有矩阵(图像、内核、结果)都以列优先方式存储,这就是为什么我以这种方式循环它们,以便它们在内存中更接近(听说这会有所帮助)。
我知道实现非常幼稚,但由于它是用 C 编写的,我希望性能会很好,但结果有点令人失望。我用大小为 100^3 的图像和大小为 10^3 的内核对其进行了测试(如果只计算乘法和加法,总共 ~1GFLOPS),它花了 ~7 秒,我认为这远低于典型 CPU 的能力。
如果可能的话,你们能帮我优化这个程序吗? 我愿意接受任何可以提供帮助的东西,如果您可以考虑的话,请提供以下几点:
-
我正在处理的问题可能很大(例如,大小为 200 x 200 x 200 的图像,内核大小为 50 x 50 x 50 甚至更大)。我知道优化此问题的一种方法是将这个问题转换为矩阵乘法问题并使用 blas GEMM 例程,但恐怕内存无法容纳如此大的矩阵
-
由于问题的性质,我更喜欢直接卷积而不是 FFTConvolve,因为我的模型是在考虑直接卷积的情况下开发的,我对 FFT 卷积的印象是它给出的结果与直接卷积略有不同,特别是对于快速改变形象,我试图避免的差异。 也就是说,我绝不是这方面的专家。因此,如果您有一个基于 FFTconvolve 的出色实现和/或我对 FFT convolve 的印象完全有偏见,如果您能帮助我,我将不胜感激。
-
假设输入图像是周期性的,因此需要周期性填充
-
我知道使用 blas/SIMD 或其他较低级别的方法在这里肯定会有很大帮助。但由于我是这里的新手,我真的不知道从哪里开始......如果你有这些图书馆的经验,如果你能帮助我指出正确的方向,我将不胜感激,
非常感谢您的帮助,如果您需要有关问题性质的更多信息,请告诉我
【问题讨论】:
-
这可能属于代码审查交流。
-
听起来您可能已经在检查 CR... 请参阅 "Which Site?" 和 "Code Review or not?"
-
初步想法 - 每个循环或至少每个图像的数据似乎非常独立。可能值得启动一些线程并将工作分配给内核。除此之外,我还会仔细检查输出,看看您是否在系统上获得了 SIMD 帮助。将标志构建到 -O3 等。最后,请务必对此进行分析,以便了解您的慢点在哪里。似乎它也对 GPU 友好。
-
@PeterCordes 感谢您的想法!我尝试在 mod 函数中使用条件减法/加法而不是 % ,并且确实有大约 20% 的加速。谢谢!
-
您可以查看汇编输出(通常为-S)并确保您看到 SIMD 指令出现。这是针对 X86、ARM 还是其他什么的?对于线程 ID,如果您以前没有这样做过,那么在 C 中设置线程并非易事。最简单的方法可能是使用 forks 运行您的应用程序,或者同时使用不同的文件运行多个应用程序,以便操作系统为您处理它。
标签: c optimization simd convolution blas