6

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

所以我这样做:

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

但这只是一种直觉,说“防止length()每次都调用函数”。这是真的吗?或者在时间要求方面与此相同:

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

谢谢你的帮助!

4

3 回答 3

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

依次显示数组的列。同样,您可以提供一个单元格数组,它将遍历单元格(注意,索引是单元格,而不是单元格内的元素)。

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

for vElement = v
    doSomething(vElement)
end

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

当然,根据应用程序,矢量化doSomething和调用doSomething(v).

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

于 2014-05-15T13:28:15.220 回答
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最好N,但它似乎并没有太大的区别。我无法解释这一点,也许这个测试有缺陷,但你去吧。

这是代码:

clear
N=5e4;
A=rand(1e2);
tic()
vector=ones(1,N);
for i=1:length(vector)
    inv(A);
end
toc()
tic()
for i=1:N
    inv(A);
end
toc()
tic()
vector=ones(1,N);
for i=1:numel(vector)
    inv(A);
end
toc()
于 2014-05-15T12:32:54.523 回答
1

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

根据定义,Matlabfor循环仅在循环开始时对其参数表达式求值一次,以预先计算循环索引变量 ( i) 的值范围或数组,以在循环过程中使用。与许多其他语言不同,Matlabfor不会在每次通过循环时重新评估某些循环控制语句。(这也是为什么在 Matlab 循环体内分配循环索引变量for没有效果,在 C 或 Java 中,它允许您“跳来跳去”并改变控制流。)

阅读 Matlab中的文档。它可能会更明确,但您会注意到它是根据表达式解析为的值定义的,而不是表达式本身。

语法等价

交流for回路被定义为具有这种行为。

/* C-style for loop */
for ( A; B; C; ) {
    ...
}

/* basically equivalent to: */
{
    A;
    while ( B ) {
        ....
        C;
    }
}

在功能上,Matlab 的for循环语法等价性更像这样。

% 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在实践中,Matlab 可以在原始值的情况下优化具体数组的创建。循环体与控制表达式的这种分离也有助于支持parfor并行计算工具箱使用的并行循环,因为每次循环迭代的循环索引变量的值在循环开始之前是已知的,并且独立于任何循环通过。

示范

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

function show_for_behavior

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

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

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

>> show_for_behavior
three() got called
     1
     2
     3

语言原因

这是我推测的地方。

除了方便之外,我怀疑 Matlab 以它的方式定义其for循环的原因之一,而不是在常规while循环上为您提供 C 风格的语法糖,是由于浮点舍入,很难正确获取索引变量。默认情况下,您正在使用的数字循环变量是双精度数,并且对于x(大约 10^15)的较大值x + 1 == x,因为 x ( ) 处的相对精度eps(x)大于 1。

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

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

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

% 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

只有提前为所有通道定义了 min、max 和 step 的范围,您才能执行此操作,而更灵活的while-loop 形式无法保证这一点。

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

这是一个显示此行为的示例。

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

当我运行它时,我会根据epsMatlab 的循环方式进行更改。

>> big_for_loop
b - a = 65536.000000
1 + (b - a) / step = 4370.066667
i advanced
i advanced
i advanced
i advanced
niters = 4370
于 2014-05-15T23:50:04.570 回答