有没有办法避免循环使这段代码更快?

时间:2016-03-30 10:16:27

标签: matlab for-loop vectorization

有没有办法避免循环使这段代码更快?

"无功"是理想的结果。

" AA"和" BB"是值已知的向量。

此代码的四个主要行基于逻辑:value="23"00011011 {{1} }和0 <=

1

3 个答案:

答案 0 :(得分:3)

我设法将代码矢量化为一行代码!从几秒钟下降到毫秒以下:

out = [0 0 AA(3:end) + ([1 2] * (diff(BB(hankel(1:3, 3:numel(BB)))) > 0)).*50011];

要了解如何到达那里,让我们逐步改进原始代码。

1)

首先,我们从你拥有的双循环开始:

tic
var0 = zeros(size(AA));
for i=3:numel(AA)
    for j=1:N
        if AA(i) == j && BB(i)<=BB(i-1) && BB(i-1)<=BB(i-2)
            var0(i)=j;
        else
            if AA(i) == j && BB(i)<=BB(i-1) && BB(i-1)>BB(i-2)
                var0(i)=j+50011;
            else
                if AA(i) == j && BB(i)>BB(i-1) && BB(i-1)<=BB(i-2)
                    var0(i)=j+2*50011;
                else
                    if AA(i) == j && BB(i)>BB(i-1) && BB(i-1)>BB(i-2)
                        var0(i)=j+3*50011;
                    end
                end
            end
        end
    end
end
toc

2)

正如@SpamBot所指出的,嵌套的if / else语句可以通过链接来简化它们。您还要多次评估相同的测试AA(i)==j。如果测试为假,则跳过整个for循环。因此我们可以消除第二个for循环,并直接使用j=AA(i)

以下是新代码:

tic
var1 = zeros(size(AA));
for i=3:numel(AA)
    j = AA(i);
    if BB(i)<=BB(i-1) && BB(i-1)<=BB(i-2)
        var1(i) = j;
    elseif BB(i)<=BB(i-1) && BB(i-1)>BB(i-2)
        var1(i) = j + 50011;
    elseif BB(i)>BB(i-1) && BB(i-1)<=BB(i-2)
        var1(i) = j + 2*50011;
    elseif BB(i)>BB(i-1) && BB(i-1)>BB(i-2)
        var1(i) = j + 3*50011;
    end
end
toc

这是一项巨大的改进,代码将在原始时间的一小部分内运行。我们还能做得更好......

3)

正如您在问题中提到的,if / else条件对应于模式00, 01, 10, 11,其中0/1或false / true是对相邻数字执行二进制x&gt; y测试的结果。

使用这个想法,我们得到以下代码:

tic
var2 = zeros(size(AA));
for i=3:numel(AA)
    val = (BB(i) > BB(i-1)) * 10 + (BB(i-1) > BB(i-2));
    switch (val)
        case 00
            k = 0;
        case 01
            k = 50011;
        case 10
            k = 2*50011;
        case 11
            k = 3*50011;
    end
    var2(i) = AA(i) + k;

end
toc

4)

让我们用表查找操作替换该switch语句。这给了我们这个新版本:

tic
v = [0 1 2 3] * 50011;  % 00 01 10 11
var3 = zeros(size(AA));
for i=3:numel(AA)
    var3(i) = AA(i) + v((BB(i) > BB(i-1))*2 + (BB(i-1) > BB(i-2)) + 1);
end
toc

5)

在这个最终版本中,我们可以通过注意每个迭代以滑动窗口方式访问切片BB(i-2:i)来完全摆脱循环。我们可以use the hankel function to create a sliding window整齐地BB(每个作为一列返回)。

接下来,我们使用diff执行矢量化比较,然后将两个测试的结果0/1映射为[0 1 2 3]*50011值。最后,我们适当地添加向量AA

这给了我们最终的单行,完全矢量化:

tic
var4 = [0, 0, AA(3:end) + ([1 2] * (diff(BB(hankel(1:3, 3:numel(BB)))) > 0)).*50011];
toc

比较

为了验证上述解决方案,我使用以下随机向量作为测试数据:

N = 50011;
AA = randi(N, [1 2000]);
BB = randi(N, [1 2000]);

assert(isequal(var0,var1,var2,var3,var4))

我按以下顺序匹配解决方案的时间如下:

>> myscript  % tested in MATLAB R2014a
Elapsed time is 1.663210 seconds.
Elapsed time is 0.000111 seconds.
Elapsed time is 0.000099 seconds.
Elapsed time is 0.000089 seconds.
Elapsed time is 0.000417 seconds.

>> myscript  % tested in MATLAB R2015b (with the new execution engine)
Elapsed time is 2.816541 seconds.
Elapsed time is 0.000233 seconds.
Elapsed time is 0.000158 seconds.
Elapsed time is 0.000157 seconds.
Elapsed time is 0.000339 seconds.

希望这篇文章不会太长,我只是想通过逐步改变来展示如何解决这类问题。

现在选择您最喜欢的解决方案:)

答案 1 :(得分:2)

我认为最重要的改进是在循环j时只评估AA(i)==j一次。

此外,即使只有最后一次覆盖是相关的,您也可能经常覆盖var(i)。考虑只考虑一个j == AA(i)并仅为此做if-else。基本上,您在AA中搜索j,而是使用find(AA == j, 1)

作为旁注,你有不必要的if / end块,它们可以直接进入父else块。

答案 2 :(得分:1)

这是一种主要使用logical indexing -

的矢量化方法
%// Get size parameter
N = numel(AA)-2;
limit = 50011;

%// Get differentiation across BB
dB = diff(BB);

%// Construct an ID array with different valus for various conditions
id = ones(N,1);
id((dB(2:end) <= 0) & (dB(1:end-1) > 0)) = 2;
id((dB(2:end) > 0) & (dB(1:end-1) <= 0)) = 3;
id((dB(2:end) > 0) & (dB(1:end-1) > 0)) = 4;

%// Get scaled values as used under various IF statements
vals = ((id-1)*50011) + AA(3:end);

%// Get a valid mask that would be used to set values from vals into output
valid_mask = AA(3:end) <= limit;

%// Setup output array and selectively set values from vals using valid_mask
var_out = zeros(1,N);
var_out(valid_mask) = vals(valid_mask);

请注意,原始输出的前两个元素始终为零。使用所提出的解决方案的输出跳过前两个元素以避免冗余。如果需要与旧范例保持一致,请在开始时填充两个零 -

final_out = [0 0 var_out];