对单元数组的每个元素执行算术运算的最快方法是什么?

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.这使用了mxUnshareArrayAmro建议的无证件.

#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)

Amr*_*mro 3

不完全是答案,但这是一种查看 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 函数是正确的选择:)