dig*_*ion 5 arrays performance matlab loops
假设我想将一个单元格数组的每个元素A
与一个系数相乘k
.我可以这样做:
A = cellfun(@(x) k*x, A, 'UniformOutput', false)
Run Code Online (Sandbox Code Playgroud)
但这非常缓慢.有更快更好的方法吗?单元阵列元素是可变长度向量,因此cell2num
不适用.
编辑:基于fpe对for循环的推荐,这是一个示例基准.从这些数据开始
A = arrayfun(@(n) rand(n,1), randi(5,1000,1000), 'UniformOutput',false);
Run Code Online (Sandbox Code Playgroud)
cellfun
上面的调用需要9.45 seconds
,而for循环:
A2 = cell(size(A));
for i = 1:size(A,1), for j = 1:size(A,2), A2{i,j} = A{i,j}*k; end; end
A = A2;
Run Code Online (Sandbox Code Playgroud)
需要1.67 seconds
,这是一个重大的进步.我仍然更喜欢几个数量级的东西.(我也不明白为什么Matlab解释器无法像for循环一样快地进行cellfun调用.它们在语义上是相同的.)
编辑2: Amro建议制作一个单循环的速度要快得多:
for i = 1:numel(A), A{i} = A{i}*k; end
Run Code Online (Sandbox Code Playgroud)
需要1.11 seconds
,如果我pack
在它之前运行,只是调整内存0.88 seconds
.
实现MEX函数实现这一点实际上并没有好多少:0.73 seconds
,(0.53 seconds
之后pack
),这表明在Matlab中分配许多小矩阵的速度很慢.
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
if (nrhs != 2)
mexErrMsgTxt("need 2 arguments (Cell, Coefficient)");
mwSize const* size = mxGetDimensions(prhs[0]);
int N = mxGetNumberOfDimensions(prhs[0]);
if (mxGetNumberOfElements(prhs[1]) != 1)
mexErrMsgTxt("second argument to multcell must be a scalar");
double coefficient = *mxGetPr(prhs[1]);
plhs[0] = mxCreateCellArray(N, size);
int M = mxGetNumberOfElements(prhs[0]);
for (int i = 0; i < M; i++) {
mxArray *r = mxGetCell(prhs[0], i);
mxArray *l = mxCreateNumericArray(mxGetNumberOfDimensions(r),
mxGetDimensions(r),
mxDOUBLE_CLASS,
mxREAL);
double *rp = mxGetPr(r);
double *lp = mxGetPr(l);
int num_elements = mxGetNumberOfElements(r);
for (int i = 0; i < num_elements; i++)
lp[i] = rp[i] * coefficient;
mxSetCell(plhs[0], i, l);
}
}
Run Code Online (Sandbox Code Playgroud)
作弊了一下,但并实施实际编辑内存MEX功能到位似乎是获得合理表现出来的的唯一途径:0.030 seconds
.这使用了mxUnshareArray
Amro建议的无证件.
#include "mex.h"
extern "C" bool mxUnshareArray(mxArray *array_ptr, bool noDeepCopy);
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
if (nrhs != 2)
mexErrMsgTxt("need 2 arguments (Cell, Coefficient)");
mwSize const* size = mxGetDimensions(prhs[0]);
int N = mxGetNumberOfDimensions(prhs[0]);
if (mxGetNumberOfElements(prhs[1]) != 1)
mexErrMsgTxt("second argument to multcell must be a scalar");
double coefficient = *mxGetPr(prhs[1]);
mxUnshareArray(const_cast<mxArray *>(prhs[0]), false);
plhs[0] = const_cast<mxArray *>(prhs[0]);
int M = mxGetNumberOfElements(prhs[0]);
for (int i = 0; i < M; i++) {
mxArray *r = mxGetCell(prhs[0], i);
double *rp = mxGetPr(r);
int num_elements = mxGetNumberOfElements(r);
for (int i = 0; i < num_elements; i++)
rp[i] = rp[i] * coefficient;
}
}
Run Code Online (Sandbox Code Playgroud)
不完全是答案,但这是一种查看 JIT 编译器和加速器在两种方法(cellfun 与 for-loop)中的影响的方法:
feature('jit', 'off'); feature('accel', 'off');
tic, A = cellfun(@(x) k*x, A, 'UniformOutput', false); toc
tic, for i=1:numel(A), A{i} = A{i}*k; end, toc
feature('jit', 'on'); feature('accel', 'on');
tic, A = cellfun(@(x) k*x, A, 'UniformOutput', false); toc
tic, for i=1:numel(A), A{i} = A{i}*k; end, toc
Run Code Online (Sandbox Code Playgroud)
我得到以下内容
Elapsed time is 25.913995 seconds.
Elapsed time is 13.050288 seconds.
Run Code Online (Sandbox Code Playgroud)
与
Elapsed time is 10.053347 seconds.
Elapsed time is 1.978974 seconds.
Run Code Online (Sandbox Code Playgroud)
第二个打开优化。
顺便说一句,并行的parfor
性能要差得多(至少在我的本地测试机器上,池大小为 2 个进程)。
看到您发布的结果,MEX 函数是正确的选择:)