我对C很新,在我的大部分研究中,没有太多需要比python更快的东西。然而,事实证明我最近所做的工作需要计算相当大的向量/矩阵,因此可能需要C + MPI解决方案。
从数学上讲,任务很简单。我有很多维数〜40k的向量,并希望计算这些向量中所选对的Kronecker Product,然后对这些kronecker产品求和。
问题是,如何有效地做到这一点?以下代码结构有什么问题,使用for循环或获得效果吗?
下面描述的函数kron
传递长度为A
的向量B
和vector_size
,并计算它们存储在C
中的kronecker积,a vector_size*vector_size
矩阵。
void kron(int *A, int *B, int *C, int vector_size) {
int i,j;
for(i = 0; i < vector_size; i++) {
for (j = 0; j < vector_size; j++) {
C[i*vector_size+j] = A[i] * B[j];
}
}
return;
}
这对我来说似乎很好,当然(如果我没有做出一些愚蠢的语法错误)产生正确的结果,但我有一种潜在的怀疑,即嵌入式循环不是最佳的。如果我还有另一种方法,请告诉我。建议欢迎。
感谢您的耐心和任何建议。再一次,我对C非常缺乏经验,但谷歌搜索给我带来了这个问题的一点快乐。
答案 0 :(得分:6)
由于你的循环体完全独立,因此肯定有一种方法可以加速它。在考虑MPI之前,最简单的就是利用几个核心。 OpenMP应该做得很好。
#pragma omp parallel for
for(int i = 0; i < vector_size; i++) {
for (int j = 0; j < vector_size; j++) {
C[i][j] = A[i] * B[j];
}
}
现在许多编译器都支持它。
您也可以尝试将一些常见的表达式拖出内部循环,但是像gcc,icc或clang这样的优秀编译器应该自己完成这些:
#pragma omp parallel for
for(int i = 0; i < vector_size; ++i) {
int const x = A[i];
int * vec = &C[i][0];
for (int j = 0; j < vector_size; ++j) {
vec[j] = x * B[j];
}
}
BTW,用int
索引通常不是正确的做法。对于与索引和对象大小有关的所有内容,size_t
是正确的typedef
。
答案 1 :(得分:3)
对于双精度向量(单精度和复数相似),您可以使用BLAS例程DGER
(排名一更新)或类似的方法一次一个地进行产品,因为他们都在向量上。你乘以多少个向量?请记住,添加一堆矢量外部产品(您可以将Kronecker产品视为)将最终作为矩阵矩阵乘法,BLAS的DGEMM
可以有效处理。如果你真的需要整数运算,你可能需要编写自己的例程。
答案 2 :(得分:2)
如果您的编译器支持C99(并且您从未传递与A
和B
相同的向量),请考虑在支持C99的模式下编译并将函数签名更改为:
void kron(int * restrict A, int * restrict B, int * restrict C, int vector_size);
restrict
关键字向编译器承诺A
,B
和C
指向的数组不会别名(重叠)。在编写代码时,编译器必须在内循环的每次执行时重新加载A[i]
,因为它必须保守,并假设您的C[]
存储可以修改A[]
中的值。在restrict
下,编译器可以假设这不会发生。
答案 3 :(得分:2)
找到解决方案(感谢@Jeremiah Willcock):GSL's BLAS bindings似乎很精彩。如果我们逐步选择一对向量A
和B
并将它们添加到某个“运行总计”向量/矩阵C
,则以上修改后的kron函数版本
void kronadd(int *A, int *B, int *C, int vector_size, int alpha) {
int i,j;
for(i = 0; i < vector_size; i++) {
for (j = 0; j < vector_size; j++) {
C[i*vector_size+j] = alpha * A[i] * B[j];
}
}
return;
}
从功能上讲,精确对应BLAS DGER函数(可以gsl_blas_dger访问)。最初的kron
函数是DGER,alpha = 0
和C
是未正确的(归零)矩阵/正确维度的向量。
事实证明,最终可能更容易为这些库简单地使用python绑定。但是,我想我在尝试解决这些问题时已经学到了很多东西。在其他回复中有一些更有用的建议,如果您遇到同样的问题,请查看它们。谢谢大家!
答案 4 :(得分:1)
这是数值计算圈中常见的问题,最好的办法是使用经过良好调试的软件包,如Matlab(或其Free Software clones之一)。
你甚至可能找到一个python binding,所以你可以摆脱C。
以上所有内容(可能)都比严格在python中编写的代码快。如果你需要更快的速度,我会建议一些事情:
答案 5 :(得分:1)
另一个易于实现的优化是,如果你知道数组的内部维度可以被n整除,那么将n个赋值语句添加到循环体中,减少必要的迭代次数,并进行相应的更改循环计数。
这个策略可以通过在外部循环周围使用switch语句来推广,其中数组大小的大小可以被2,3,4和5整除,或者是最常见的。这可以带来相当大的性能,并且与建议1和3兼容,以进一步优化/并行化。一个好的编译器甚至可以为你做这样的事情(也就是循环展开)。
另一个优化是利用指针算法来避免数组索引。这样的事情可以解决问题:
int i, j;
for(i = 0; i < vector_size; i++) {
int d = *A++;
int *e = B;
for (j = 0; j < vector_size; j++) {
*C++ = *e++ * d;
}
}
这也避免了通过将其缓存在局部变量中多次访问A [i]的值,这可能会给你一个小的速度提升。 (请注意,此版本不可并行化,因为它会改变指针的值,但仍可用于循环展开。)
答案 6 :(得分:0)
为解决您的问题,我认为您应该尝试使用Eigen 3,它是一个使用所有矩阵函数的C ++库!
如果有时间,请查看其文档! =)
祝你好运!