Cellfun与简单的Matlab循环性能比较

8
当我在大学里开始使用matlab时,我的导师会因为看到任何不必要的循环而批评我(他会要求将其替换为kron或任何可能的索引操作)。后来,我尽量避免在matlab中使用循环,寻找最黑暗的matlab编码方式来进行黑魔法,而不是简单的循环。
有一天,我发现了cellfun,这使得黑魔法变得更加简单,我可以通过单元格和cellfun组合来改变许多循环,但是有一天我看到了一个关于cellfun的帖子,这让我质疑我所继承的matlab知识是否正确,即:matlab循环总是比内置编译函数慢,这是我非常信任的事情。我在我的一个实现中进行了测试,实际上,for循环会更快!我就像,哦,所有那些日子做晦涩的代码都白费了哈哈哈。从那天起,我停止了努力优化matlab代码,通常这取决于每种情况等等。

今天我看到了这个答案,它让我想起了我为避免使用尽可能多的Matlab循环所做的努力(我不知道作者是否出于性能考虑而避免使用循环,但无论如何,它都让我想起了所有这些Matlab循环性能问题)。然后我想到了一个问题:cellfun比for循环更好吗?这什么情况下是真的?


1
关于你提到的我的回答:是的,我尽量避免使用for循环,就像你一样(或曾经一样),我的动机是为了性能。我认为(或曾经认为?)for循环通常较慢,应该避免使用。 - Luis Mendo
显然,cellfun在特殊情况下除外,速度较慢。请参见http://www.mathworks.com/matlabcentral/answers/42335和http://www.mathworks.com/matlabcentral/newsreader/view_thread/301894。 - Luis Mendo
@LuisMendo 是的,完全正确!但不幸的是,使用匿名函数处理的 cellfun 通常比 for 循环慢...这会让我为了无用的努力感到无聊。感谢您提供的参考资料。 - Werner
1
很遗憾,我才几天前发现了cellfunarrayfun,而我正在逐渐掌握它们的用法... - Luis Mendo
4个回答

11

如果性能是一个重要因素,你应该避免使用单元格、循环或cellfun/arrayfun函数。假设可以使用向量运算,通常使用向量操作速度更快。

下面的代码扩展了Werner的add示例,使用了标准的数组循环和数组操作。

结果为:

  • 单元循环时间 - 0.1679
  • Cellfun 时间 - 2.9973
  • 循环数组时间 - 0.0465
  • 数组时间 - 0.0019

代码:

nTimes = 1000;
nValues = 1000;
myCell = repmat({0},1,nValues);
output = zeros(1,nValues);

% Basic operation
tic;
for k=1:nTimes
  for m=1:nValues
    output(m) = myCell{m} + 1;
  end
end
cell_loop_timeAdd=toc;    
fprintf(1,'Cell Loop Time %0.4f\n', cell_loop_timeAdd);

tic;        
for k=1:nTimes
  output = cellfun(@(in) in+1,myCell);
end
cellfun_timeAdd=toc;
fprintf(1,'Cellfun Time %0.4f\n', cellfun_timeAdd);


myData = repmat(0,1,nValues);
tic;
for k=1:nTimes
  for m=1:nValues
    output(m) = myData(m) + 1;
  end
end
loop_timeAdd=toc;
fprintf(1,'Loop Array Time %0.4f\n', loop_timeAdd);

tic;
for k=1:nTimes
    output = myData + 1;
end
array_timeAdd=toc;
fprintf(1,'Array Time %0.4f\n', array_timeAdd);

很好,不错的点和贡献。只有一个细节:cellfun tic toc也包含fprintf函数。这不会影响结果,但无论如何... - Werner

4
我会添加一个回答,包含我自己测试的结果,但如果其他人能够贡献他们的知识,我将不胜感激。这只是我进行的一个简单测试。
我已经使用1000的单元格大小和1000次循环测试了以下条件(总时间的结果),我可能需要运行超过1000次,因为结果有些波动,但无论如何,这不是一篇科学文章。
  • 基本操作(求和)
    • 简单的for循环:0.2663秒
    • cellfun:9.4612秒
  • 字符串操作(strcmp)
    • 简单的for循环:1.3124秒
    • cellfun:11.8099秒
  • 内置函数(isempty)
  • 非均匀数据(regexp)
    • 简单的for循环:24.2157秒
    • cellfun(字符串输入):44.0424秒

因此,似乎使用匿名函数调用的cellfun比简单的for循环慢,但如果您将使用Matlab内置方法,请使用cellfun并将其与字符串引号一起使用。这并非对所有情况都成立,但至少对于测试函数而言是如此。

实施的测试代码(我远非优化专家,所以在这里提供代码以防我做错了什么):

function ...
  [loop_timeAdd,cellfun_timeAdd,...
  loop_timeStr,cellfun_timeStr,...
  loop_timeBuiltIn,cellfun_timeBuiltInStrInput,...
  cellfun_timeBuiltyInFcnHandle,...
  loop_timeNonUniform,cellfun_timeNonUniform] ...
  = test_cellfun(nTimes,nCells)

myCell = repmat({0},1,nCells);
output = zeros(1,nCells);

% Basic operation
tic;
for k=1:nTimes
  for m=1:nCells
    output(m) = myCell{m} + 1;
  end
end
loop_timeAdd=toc;

tic;
for k=1:nTimes
  output = cellfun(@(in) in+1,myCell);
end
cellfun_timeAdd=toc;

% String operation
myCell = repmat({'matchStr'},1,nCells); % Add str that matches
myCell(1:2:end) = {'dontMatchStr'}; % Add another str that doesnt match
output = zeros(1,nCells);

tic;
for k=1:nTimes
  for m=1:nCells
    output(m) = strcmp(myCell{m},'matchStr');
  end
end
loop_timeStr=toc;

tic;
for k=1:nTimes
  output = cellfun(@(in) strcmp(in,'matchStr'),myCell);
end
cellfun_timeStr=toc;

% Builtin function (isempty)
myCell = cell(1,nCells); % Empty
myCell(1:2:end) = {0}; % not empty
output = zeros(1,nCells);

tic;
for k=1:nTimes
  for m=1:nCells
    output(m) = isempty(myCell{m});
  end
end
loop_timeBuiltIn=toc;

tic;
for k=1:nTimes
  output = cellfun(@isempty,myCell);
end
cellfun_timeBuiltyInFcnHandle=toc;

tic;
for k=1:nTimes
  output = cellfun('isempty',myCell);
end
cellfun_timeBuiltInStrInput=toc;

% Builtin function (isempty)
myCell = repmat({'John'},1,nCells);
myCell(1:2:end) = {'Doe'};
output = cell(1,nCells);

tic;
for k=1:nTimes
  for m=1:nCells
    output{m} = regexp(myCell{m},'John','match');
  end
end
loop_timeNonUniform=toc;

tic;
for k=1:nTimes
  output = cellfun(@(in) regexp(in,'John','match'),myCell,...
    'UniformOutput',false);
end
cellfun_timeNonUniform=toc;

1

以下是我通常决定使用哪个解决方案的方法:

  1. 能否使用简单的矩阵运算完成?如果可以,那就这么做吧。它将是最快的,并且通常更易读。
  2. 我是否在做一些简单的事情?如果是,请继续进行第3步,否则请进行第4步。
  3. 能否使用 bsxfun 完成任务?如果可以,那就这么做吧。它将非常快速并且易读。
  4. 否则,请使用简单的 for 循环。

正如您所注意到的,我几乎从不使用 cellfun 来提高性能。这是由于以下逻辑:

  1. cellfun 通常不比循环快多少,而且主要用于处理单元格。
  2. 如果性能很重要,您可能希望完全避免使用单元格,因此您不需要使用 cellfun。

0
clear all;
ntimes = 1000;

r = 100;
c = 100;
d = 100;
A = rand(r, c, d);
B = rand(r, c, d);

tic
for i = 1:ntimes
    for j = 1 : d
        result = A(:, :, j) * B(:, :, j);
    end
end
toc

A_cell = num2cell(A, [1 2]);
B_cell = num2cell(B, [1 2]);
tic
for i = 1 : ntimes
    result2 = cellfun(@(x, y) x*y, A_cell, B_cell, 'uni', 0);
end
toc

也许可以试试这个。多测试几次。平均而言,cellfun比双重循环更快。


你在注释中写的“这是在做什么?”的外部循环应该重复测量循环n次,以减少结果偏差并趋向于平均执行时间。如果像你所做的那样将其删除,则会将1次执行时间与重复n次的其他循环进行比较。 - Werner
@Werner 我已经更新了代码。我在争论,因为我在我的项目中做着类似的事情。我已经在我的项目中测试了双重循环版本和cellfun版本,发现cellfun比双重循环快得多。我不知道为什么。但这个例子可以表明cellfun更快。 - user3390652
你的比较似乎不太公平,因为单元格的尺寸是1x2,而rand则是100x100x100。此外,我已经有一段时间没有使用Matlab了,但是似乎你可以通过bsxfun来替换for j = 1:d; result = A()…,这可能会更快。 - Werner
1
@Werner 不是1x2,而是通过保持维度1和2来转换为单元格。但我同意bsxfun会表现更好。 - user3390652
我明白了,我错过了num2cell(A...)。我运行了你的示例代码并得到了这些基准测试结果:数组21.265852秒;单元格:21.795821秒。我使用的是Matlab 2013。无论如何,这个主题的想法正是要说,如果你需要优化,那么在尝试做一些非常复杂的事情之前,你应该优化测试方法。在你的特定情况下,似乎没有什么区别。 - Werner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接