在循环中使用length()是否有效?

时间:2014-05-15 12:05:02

标签: performance matlab loops time-complexity variable-length

当我在MATLAB中迭代一个向量时,我有很多情况,

所以我这样做:

len = length(vector)
for i=1:len
 do_something();
end

但这只是一种直觉,表示"阻止每次调用length()功能"。这是真的吗?或者在时间要求方面与此相同:

for i=1:length(vector)
 do_something();
end

感谢您的帮助!

3 个答案:

答案 0 :(得分:7)

如果你担心性能或速度 - 不要担心,真的。它不会产生任何明显的差异。 @David在他的回答中提供了一些时间来支持这一点。

就美学而言,我通常会写出值得的东西

for i = 1:numel(v)
    doSomething(v(i))
end

我倾向于选择numellength,因为length会为您提供最长数组维度的长度,而不是第一维的长度。如果你总是确定它是矢量它们是相同的,但我尽量避免这种假设。

当然,如果我需要单独访问numel(v),那么值得将其提取为中间变量vNum并撰写for i = 1:vNum

如果您对for循环索引的语义感兴趣,那么它比您想象的要复杂得多。当您编写for i = 1:N时,MATLAB并不总是简单地创建向量1:N,然后迭代它。例如,您可以写

for i = 1:realmax
    i
end

for i = 1:Inf
    i
end

甚至

for i = Inf:-1:1
    i
end

它将起作用(按 Ctrl-C 退出)。在这种情况下,MATLAB不会将循环索引创建为向量,但它不能,因为它太大了。

解析器具有比这更复杂的逻辑,具有多个边缘情况和优化的知识。有时它会在循环之前创建循环索引,有时会在运行中。我保证你无法猜测所有的内部结构,除非你有权访问MATLAB源代码。

另请注意,循环索引不必是向量;如果你提供一个数组,MATLAB循环遍历列。例如,

for i = magic(3)
    i
end

依次显示数组的列。类似地,您可以提供一个单元格数组,它将遍历单元格(NB索引是单元格,而不是单元格中的元素。)

出于这个原因,我有时会写

for vElement = v
    doSomething(vElement)
end

而不是上面使用numel的第一个模式。

当然,根据应用程序的不同,向doSomething进行矢量化并调用doSomething(v)可能会更好。

最后一件事 - 我们到目前为止谈论的是整数索引,但请记住冒号运算符可以有任何增量,例如pi:sqrt(2):10;并记住结肠操作符与linspace不同,并且不会给出相同的答案。

答案 1 :(得分:1)

我进行了测试,首先使用JIT编译(我每次测试3次)

length(vector) 12.86 12.50 12.44    
N              13.00 12.52 12.55   
numel(vector)  12.83 12.55 12.56

关闭JIT编译,

length(vector) 13.12 13.43 12.95   
N              12.54 13.04 13.00   
numel(vector)  12.57 12.92 12.72

我得出结论,通过JIT编译,你做的事情并不重要,但没有JIT,numel的{​​{1}}是最好的,但它似乎没有一个很大的区别。我无法解释这一点,也许这个测试存在缺陷,但是你去了。

以下是代码:

N

答案 2 :(得分:1)

他们是一样的。在Matlab的特定情况下,不要担心:在length()初始化子句中放置for或任何其他函数总是和在循环外评估它一样快,因为{{ 1}}只会在任何一种情况下调用它。你的直觉可能基于其他一些语言' for循环,如C和Java,它们具有不同的行为。

根据定义,Matlab for循环仅在循环开始时计算其参数表达式一次,以预先计算循环索引变量(for)的值的范围或数组在循环内部进行传递。与许多其他语言不同,Matlab的i不会在每次循环时重新评估一些循环控制语句。 (这也是为什么在Matlab for循环体内分配循环索引变量没有效果的原因,在C或Java中它允许你跳转到#34;并改变控件流。)

阅读Matlab for documentation。它可以更加明确,但你会注意到它是根据表达式解析的值来定义的,而不是表达式本身。

语法等值

定义C for循环具有此行为。

for

从功能上讲,Matlab的/* C-style for loop */ for ( A; B; C; ) { ... } /* basically equivalent to: */ { A; while ( B ) { .... C; } } 循环语法等价更像是这样。

for

实际上,在原始值的情况下,Matlab可以优化具体数组% Matlab for loop for i = A:B ... end % basically functionally equivalent to: tmp_X = A:B; % A and B only get evaluated outside the loop! tmp_i = 1; % tmp_* = an implicit variable you have no access to while tmp_i < size(tmp_X,2) i = tmp_X(:,tmp_i); ... tmp_i = tmp_i + 1; end 的创建。循环体与控制表达式的这种分离也有助于支持并行计算工具箱使用的并行tmp_X循环,因为每个循环迭代的循环索引变量的值在循环开始之前是已知的,并且独立于执行任何循环传递。

示范

您可以使用在循环控制子句中具有可观察副作用的函数来自行确认此行为。

parfor

你可以看到整个循环只有一次调用。

function show_for_behavior

for i = 1:three(NaN)
    disp(i);
end

function out = three(x)
disp('three() got called');
out = 3;

语言原因

这里我推测了一下。

除了方便之外,我怀疑Matlab定义其>> show_for_behavior three() got called 1 2 3 循环的原因之一,而不是在常规for循环中为你提供C风格的句法糖,它是&#39 ;由于浮点舍入,使得索引变量正确变得棘手。默认情况下,您使用的数字循环变量是双精度数,对于while(大约10 ^ 15),x的大值,因为x的相对精度({{1} }})大于1.

因此,如果您像这样对x + 1 == x进行天真的eps(x) - 循环转换,那么您将拥有无限循环,因为在每一步,while都会导致由于四舍五入,for i = A:B ... end的值相同。

i = i + 1

为了能够对大值序列执行循环,您可以计算值范围和步数,使用单独的整数值跟踪循环索引,并为每个步骤构造i值使用该计数器和步长,而不是在每次传递时递增临时变量。这样的事情。

i = A;
while (i < B)
    ...
    i = i + 1;
end

只有在所有通过的时间范围内提前定义了范围min,max和step时才能执行此操作,而使用更灵活的i - 循环形式无法保证。

请注意,在这种情况下,对于大A和B,x可能具有完全相同的值,用于多次迭代传递,但最终会进展,并且您将获得与您期望的一样多的循环迭代使用无限精度值而不是近似浮点值。我怀疑这是关于Matlab在这些情况下内部做了什么。

以下是显示此行为的示例。

% original
for x = A:S:B; ...; end

% equivalent
nSteps = int64( ((B - A) / S) ) + int64(1);
i = int64(0);
while i < nSteps
    x = A + (S * double(i));
    ....
    i = i + int64(1);
end

当我运行这个时,如果这是Matlab正在进行循环的话,我会根据while的变化进行更改。

function big_for_loop(a)

if nargin < 1;  a = 1e20;  end
b = a + 4 * eps(a);
step = 15;

fprintf('b - a = %f\n', b - a);
fprintf('1 + (b - a) / step = %f\n', 1 + (b - a) / step);

last_i = a;
n = 0;
for i = a : step : b
    n = n + 1;
    if (i ~= last_i); disp('i advanced');  end
    last_i = i;
end
fprintf('niters = %d\n', n);