Python C语言中的快速二维卷积
我正在尝试用Python实现一个卷积神经网络。最初,我使用scipy.signal的convolve2d函数进行卷积,但它有很多开销,而且只需用C实现我自己的算法并从python调用它会更快,因为我知道我的输入是什么样子的 我实现了两个功能:Python C语言中的快速二维卷积,python,c,algorithm,performance,optimization,Python,C,Algorithm,Performance,Optimization,我正在尝试用Python实现一个卷积神经网络。最初,我使用scipy.signal的convolve2d函数进行卷积,但它有很多开销,而且只需用C实现我自己的算法并从python调用它会更快,因为我知道我的输入是什么样子的 我实现了两个功能: 用不可分核卷积矩阵 用可分离内核卷积矩阵(目前我假设python在将矩阵传递到C之前进行秩检查和拆分) 这两个函数都没有填充,因为我需要降维 不可分二维卷积 与scipy.signal的convolve2d相比,这仍然是一个相当大的改进,同样的操作大约需要
>任何建议都会被赏识。
如果你在X86上运行,那么只考虑使用SSE或AVX-SIMD优化。对于
double
数据,吞吐量的提高将是适度的,但是如果您可以切换到float
,那么您可以使用SSE实现大约4倍的提高,或者使用AVX实现8倍的提高。关于StackOverflow这个主题,已经有很多问题和答案,您可以从中获得一些关于实现的想法。另外,也有许多可用的库,其中包括高性能2D卷积(滤波)例程,这些库通常利用SIMD来提高性能,例如Intel的IPP(商用)或OpenCV(免费)
另一种可能是利用多个核心-将图像分割成块,并在其自己的线程中运行每个块。例如,如果您有一个4核CPU,则将图像分割为4个块。(见附件)
当然,如果您真的想充分优化此操作,您可以将上述两种想法结合起来
可以应用于当前代码和任何未来实现(例如SIMD)的一些小优化:
- 如果内核是对称的(或奇数对称的),那么可以通过添加(减去)对称输入值并执行一次乘法而不是两次乘法来减少操作数
-
对于可分离的情况,不是分配一个完整的帧临时缓冲区,而是考虑使用一个“条带挖掘”方法——分配一个更小的缓冲器,它是全宽度的,但是相对较少的行数,然后在“条”中处理图像,交替地应用水平内核和垂直内核。这样做的好处是,您有一个更友好的缓存访问模式和更小的内存占用
关于编码风格的几点意见:
关键字多年来一直是多余的,如果你试图使用它,现代编译器会发出警告-通过舍弃它来节省一些噪音(和一些打字)register
- 在C中铸造
的结果是不受欢迎的-这是一个错误malloc
- 使任何输入参数
(即只读)并对任何永远不会出现别名的参数使用const
(例如restrict
和a
)-这不仅有助于避免编程错误(至少在result
的情况下),但在某些情况下,它可以帮助编译器生成更好的优化代码(特别是在潜在的别名指针的情况下)const
double*tmp=(double*)malloc(IMG_DIM*RESULT_DIM*sizeof(double))代码>,建议double*tmp=malloc(sizeof*tmp*IMG\u DIM*RESULT\u DIM)代码>1)cast不需要2)*tmp
比double
更易于维护3)没有int
溢出问题。您是否尝试过使用gcc选项-march
和-mtune
?@user2357112我所说的“移植”不是Python中代码的重复,我只是通过某种接口调用C代码,喜欢类型。正确的术语是什么?参见
// a - 2D matrix (as a 1D array), w - kernel
double* conv2(double* a, double* w, double* result)
{
register double acc;
register int i;
register int j;
register int k1, k2;
register int l1, l2;
register int t1, t2;
for(i = 0; i < RESULT_DIM; i++)
{
t1 = i * RESULT_DIM; // loop invariants
for(j = 0; j < RESULT_DIM; j++)
{
acc = 0.0;
for(k1 = FILTER_DIM - 1, k2 = 0; k1 >= 0; k1--, k2++)
{
t2 = k1 * FILTER_DIM; // loop invariants
for(l1 = FILTER_DIM - 1, l2 = 0; l1 >= 0; l1--, l2++)
{
acc += w[t2 + l1] * a[(i + k2) * IMG_DIM + (j + l2)];
}
}
result[t1 + j] = acc;
}
}
return result;
}
// a - 2D matrix, w1, w2 - the separated 1D kernels
double* conv2sep(double* a, double* w1, double* w2, double* result)
{
register double acc;
register int i;
register int j;
register int k1, k2;
register int t;
double* tmp = (double*)malloc(IMG_DIM * RESULT_DIM * sizeof(double));
for(i = 0; i < RESULT_DIM; i++) // convolve with w1
{
t = i * RESULT_DIM;
for(j = 0; j < IMG_DIM; j++)
{
acc = 0.0;
for(k1 = FILTER_DIM - 1, k2 = 0; k1 >= 0; k1--, k2++)
{
acc += w1[k1] * a[k2 * IMG_DIM + t + j];
}
tmp[t + j] = acc;
}
}
for(i = 0; i < RESULT_DIM; i++) // convolve with w2
{
t = i * RESULT_DIM;
for(j = 0; j < RESULT_DIM; j++)
{
acc = 0.0;
for(k1 = FILTER_DIM - 1, k2 = 0; k1 >= 0; k1--, k2++)
{
acc += w2[k1] * tmp[t + (j + k2)];
}
result[t + j] = acc;
}
}
free(tmp);
return result;
}
271.21900 ms
127.32000 ms