大量嵌套循环 - 如何让它更快?

时间:2013-12-13 22:47:09

标签: matlab nested-loops performance

由于许多嵌套循环,我的代码的这一部分(如下所示)需要很长时间才能运行。有没有办法可以避免这些嵌套循环使其运行得更快?

for k = 1:numel(UpscaledZLen.pq)
    for j = 1:numel(UpscaledRowLen.pq)
        for i = 1:numel(UpscaledColLen.pq)

            iZ = 1;
            while iZ <= UpscaledZLen.pq(k)
                for ZLag = 1:iZ

                    ihRow = 1;
                    while ihRow <= UpscaledRowLen.pq(j)
                        for hRowLag = 1:ihRow

                            ihCol = 1;
                            while ihCol <= UpscaledColLen.pq(i)
                                for hColLag = 1:ihCol
                                    temp1(hColLag) = trapz(AnalGamma(hRowLag,...
                                        1:hColLag,...
                                        ZLag));
                                end
                                temp2(ihCol) = trapz(temp1);
                                ihCol = ihCol + 1;
                            end

                            temp3(hRowLag) = trapz(temp2);
                        end
                        temp4(ihRow) = trapz(temp3);
                        ihRow = ihRow + 1;
                    end

                    temp5(ZLag) = trapz(temp4);
                end
                temp6(iZ) = trapz(temp5);
                iZ = iZ + 1;
            end

            NormVariance_AnalCorrAvg(i, j, k) = (2/((UpscaledRowLen.pq(j)*...
                UpscaledColLen.pq(i)*UpscaledZLen.pq(k))^2))*trapz(temp6);
        end
    end
end

这段代码试图实现以下整数表达式:

enter image description here

编辑(SSCCE) :对于一个简短的示例,我采用了以下变量大小,然后使用上面的代码来查看为这些特定变量大小运行所需的时间:

nRow = 4;
nCol = 4;
nZ = 4;

RowLenScale.pq = 1:nRow;
ColLenScale.pq = 1:nCol;
UpscaledRowLen.pq = RowLenScale.pq(rem(RowLenScale.pq(end), RowLenScale.pq) == 0);
UpscaledColLen.pq = RowLenScale.pq(rem(ColLenScale.pq(end), ColLenScale.pq) == 0);

ZLenScale.pq = 1:nZ;
UpscaledZLen.pq = ZLenScale.pq(rem(ZLenScale.pq(end), ZLenScale.pq) == 0);

AnalGamma = rand(nRow, nCol, nZ);

对于此示例,它只需 0.321976秒,而对于nRow = 100;nCol = 100;nZ = 20;的原始情况,它花了超过24小时仍在k = 4处运行最外层循环索引。

2 个答案:

答案 0 :(得分:7)

首先,您的代码有问题,因为临时变量未初始化,并且上次迭代中剩余的值仍然存在于下一个迭代中。在更正此问题并将while替换为for循环后,代码如下所示:

NormVariance_AnalCorrAvg = nan(numel(UpscaledColLen.pq), numel(UpscaledRowLen.pq), numel(UpscaledZLen.pq));
for k = 1 : numel(UpscaledZLen.pq)
    for j = 1 : numel(UpscaledRowLen.pq)
        for i = 1 : numel(UpscaledColLen.pq)

            temp6 = nan(1, UpscaledZLen.pq(k));
            for iZ = 1 : UpscaledZLen.pq(k)
                temp5 = nan(1, iZ);
                for ZLag = 1 : iZ
                    temp4 = nan(1, UpscaledRowLen.pq(j));
                    for ihRow = 1 : UpscaledRowLen.pq(j)
                        temp3 = nan(1, ihRow);
                        for hRowLag = 1 : ihRow
                            temp2 = nan(1, UpscaledColLen.pq(i));
                            for ihCol = 1 : UpscaledColLen.pq(i)
                                temp1 = nan(1, ihCol);
                                for hColLag = 1:ihCol
                                    temp1(hColLag) = trapz(AnalGamma(hRowLag,...
                                        1:hColLag,...
                                        ZLag));
                                end
                                temp2(ihCol) = trapz(temp1);
                            end
                            temp3(hRowLag) = trapz(temp2);
                        end
                        temp4(ihRow) = trapz(temp3);
                    end
                    temp5(ZLag) = trapz(temp4);
                end
                temp6(iZ) = trapz(temp5);
            end
            NormVariance_AnalCorrAvg(i, j, k) = (2/((UpscaledRowLen.pq(j)*...
                UpscaledColLen.pq(i)*UpscaledZLen.pq(k))^2))*trapz(temp6);

        end
    end
end

这里的初始化也会导致数组的预分配,这是加速循环的基本建议之一。但是,在这种情况下,它没有效果。

为了对代码进行基准测试,我使用rand使用您的代码段,但使用

nRow = 20;
nCol = 20;
nZ = 2;

在我的计算机上,清理后的代码需要25.5秒才能完成。


如何让它更快?好旧的矢量化:

hColLag上的最内层循环可以用cumtrapz替换,将运行时间减少到8.2秒。

temp1 = cumtrapz(AnalGamma(hRowLag, 1 : ihCol, ZLag), 2);

同样适用于ihCol上的循环,将运行时间减少到1.85秒。

temp2 = cumtrapz(cumtrapz(AnalGamma(hRowLag, 1 : UpscaledColLen.pq(i), ZLag), 2), 2);

hRowLag上的下一个外环仅用于计算几个类似的trapz。这是没有必要的,因为trapz是完全矢量化的;循环可以被一次调用替换,这会将运行时间减少到0.34秒。

temp3 = trapz(cumtrapz(cumtrapz(AnalGamma(1 : ihRow, 1 : UpscaledColLen.pq(i), ZLag), 2), 2), 2);

ihRow上的循环计算累积积分;使用cumtrapz使运行时间缩短到大约0.07秒。

temp4 = cumtrapz(trapz(cumtrapz(cumtrapz(AnalGamma(1 : UpscaledRowLen.pq(j), 1 : UpscaledColLen.pq(i), ZLag), 2), 2), 2), 1);

iZZLag上的循环应用相同的逻辑,我们得到以下代码

NormVariance_AnalCorrAvg = nan(numel(UpscaledColLen.pq), numel(UpscaledRowLen.pq), numel(UpscaledZLen.pq));
for k = 1 : numel(UpscaledZLen.pq)
    for j = 1 : numel(UpscaledRowLen.pq)
        for i = 1 : numel(UpscaledColLen.pq)
            temp6 = cumtrapz(trapz(cumtrapz(trapz(cumtrapz(cumtrapz(...
                AnalGamma(1 : UpscaledRowLen.pq(j), 1 : UpscaledColLen.pq(i), 1 : UpscaledZLen.pq(k)), ...
                2), 2), 2), 1), 1), 3);
            NormVariance_AnalCorrAvg(i, j, k) = (2/((UpscaledRowLen.pq(j)*...
                UpscaledColLen.pq(i)*UpscaledZLen.pq(k))^2))*trapz(temp6);

        end
    end
end

运行时间为0.043秒,相当于600倍。

编写所有这些嵌套的trapzcumtrapz当然非常容易出错。我使用了一个校验和应用于保存到文件的结果,以确保我的代码更改不会更改它。不过,你应该仔细检查才能完全确定。


可能更进一步。前三个for循环再次执行累积-y,因此可能需要更多cumtrapztrapz实现效率不高,因为它有很多错误检查代码。由于它的作用基本上是平均值的总和(参见trapezoidal rule),因此在整个网格上预先计算多维平均值将允许将这些调用转换为对sum的调用。比梯形规则更简单的是rectangle method,您可以计算函数的值,而不是在边缘处,而是在网格单元格的中心。当然,您可以尝试分析地部分计算积分;你可以降低网格的分辨率。

希望这有帮助!

答案 1 :(得分:0)

您可以采取一些措施来改善效果。首先,您可以使用for循环替换while循环。其次,你可以用sum替换trapz,一个集成。如果用sum替换,而不是计算每个循环中的和,而不是在最后计算总和,那么你将获得一些改进。最后,您可以提取最终的乘法计算。

伽玛函数的输入是真的(hRowLag,1:hColLag,ZLag)还是它(hRowLag,hColLag,ZLag)。如果可以对gamma函数进行矢量化,则可以构建一个网格来提供函数,而不是循环。

temp = zeros(max(UpscaledZLen.pq),max(UpscaledZLen.pq),max(UpscaledRowLen.pq),max(UpscaledRowLen.pq),max(UpscaledColLen.pq),max(UpscaledColLen.pq));
my_integral = zeros(numel(UpscaledZLen.pq), numel(UpscaledRowLen.pq), numel(UpscaledColLen.pq));

for k = 1:numel(UpscaledZLen.pq)
    for j = 1:numel(UpscaledRowLen.pq)
        for i = 1:numel(UpscaledColLen.pq)
            for iZ = 1:UpscaledZLen.pq(k)
                for ZLag = 1:iZ
                    for ihRow = 1:UpscaledRowLen.pq(j)
                        for RLag = 1:ihRow
                            for ihCol = 1:UpscaledColLen.pq(i)
                                for CLag = 1:ihCol
                                    temp(iZ,ZLag,ihRow,RLag,ihCol,CLag,:) = AnalGamma(RLag,1:CLag,ZLag);
                                end
                            end
                        end
                    end
                end
            end
            my_integral(i,j,k) = sum(temp(:));
        end
    end
end

my_z = repmat(permute(UpscaledZLen.pq(:),[3,2,1]),[numel(UpscaledRowLen.pq),numel(UpscaledColLen.pq),1]);
my_r = repmat(permute(UpscaledRowLen.pq(:),[2,1]),[numel(UpscaledRowLen.pq),1,numel(UpscaledZLen.pq)]);
my_c = repmat(UpscaledColLen.pq(:),[1,numel(UpscaledColLen.pq),numel(UpscaledZLen.pq)]);

NormVariance_AnalCorrAvg = my_integral .* 2 ./ (my_z .* my_r .* my_c).^2;