刚一条句子让wordpress bug了,拆成两篇算了
for的终极取代:多线程混合编程
我们也常会遇到并不能简答并行化的情形,例如针对数组元素的复杂的操作。这时的一个选择就是混合编程: mex, 它可以实现matlab与C/C++/Fortan之间的相互调用。matlab自带的user guide里有详细介绍。
在这里我们讨论两个小trick. 首先注意到向量化的高斯核代码中我们用到了exp(A)
,A可能是一个非常大的矩阵。而matlab却会不谙风情的先将这个大矩阵在内存里复制一次,然后再逐一算exp. 有时我们明明算好内存能存下,但matlab却报out of memory了。
我们可以用C来对内存进行直接操作。例如for(i...){A(i)=exp(A(i));}
. 不过还有另外一个问题:现在我们计算机大多是多核多线程,matlab也很早就支持了多线程加速,而简单的for只会单线程,所以mex后的代码可能会慢很多。这时的我们的一个选择就是openmp,它为共享内存的多线程提供了非常方便的实现。
下面附上一计算exp(A)的多线程代码,它无需额外内存:
#include "mex.h" #include #include #include using namespace std; void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[] ) { double *A, *pp; mwSize n, m, p, i, j; A = mxGetPr(prhs[0]); n = mxGetN(prhs[0]); // # of rows in C m = mxGetM(prhs[0]); // # of columns in C p = omp_get_num_procs(); #pragma omp parallel for private (j) \ default(shared) num_threads(p) for (i = 0; i < n; i ++) { for (j = 0; j < m; j ++) A[i*m+j] = exp(A[i*m+j]); } return; }
编译时需要稍修改下编译参数来开启openmp的支持。例如linux下gcc需要加上flag-fopenmp
,详见此说明,Windows下的visual studio的相应的flag是/openmp
.
但用这段代码时需要很小心。matlab使用了lazy copy的技术,既当我们运行命令A=B
时,matlab不会立即将B复制一份给A, 只有当A或者B被修改的时候才执行复制。如果我们用上面代码对B做指数运算,例如mex_exp(B)
, 这时matlab不知道B变了,所以复制还是不会执行,那么A就会和B一起被修改了,这也许会给后面的代码造成麻烦。
代码究竟慢在哪里
我们可以方便使用tic,toc
来知道每段代码的执行时间。但要知道每条语句的详细运行情况的话,非profile莫属。
先运行profile on
,再运行需要测试的代码,然后使用profile viewer
来查看报告。例如,上面两段不同的计算高斯核的实现的profile报告如下(为了报告更有信息,将其中的一条长语句拆开成了三句)。
根据报告我们就知道每条语句的执行情况,从而可以对其中时间耗费最多的部分进行优化。
无止尽的优化
大多人类活动就是一优化过程。无论是金钱,利益,地位,还是算法,代码,编译,硬件。但除了最优化外,我们还追求一些其他。例如我们用matlab是为了实现和调试上的方便,虽然优化能让计算机做得更快,但是却花去了我们的工作时间,而且使得代码难读懂。糟糕的实现是大忌,过度的优化也不可取。所以,我们需要在计算时间,开发时间,代码的可读性之间做一个权衡。
Recent Comments