Matlab:从parfor循环中打印进度

16

我在Matlab中运行了很多长时间的模拟,通常需要几分钟到几个小时不等,因此为了加快速度,我决定使用parfor循环同时运行这些模拟。

arglist = [arg1, arg2, arg3, arg4];

parfor ii = 1:size(arglist, 2)
    myfun(arglist(ii));
end

除了一个问题以外,一切都很好:进度打印。由于每个模拟需要很长时间,所以我通常使用类似以下的方式来打印进度:

prevlength = 0;
for ii = 1:tot_iter

    % Some calculations here

    msg = sprintf('Working on %d of %d, %.2f percent done', ii, tot_iter, ii/tot_iter);
    fprintf(repmat('\b', 1, prevlength))
    fprintf(msg);
    prevlength = numel(msg);
end

但是,如预料的那样,在parfor循环内这样做会导致混乱。

我在谷歌上搜索了很多以寻找解决方案,并找到了一堆“parfor进度打印机”,例如这个。然而,它们所有的都打印整个parfor循环的进度,而不是显示每个个别迭代已经进行到哪里。由于parfor循环中只有大约4-8次迭代,但每次迭代需要大约一个小时,所以这种方法对我来说并不是非常有用。

对我来说理想的解决方案应该是像这样的:

Working on 127 of 10000, 1.27 percent done
Working on 259 of 10000, 2.59 percent done
Working on 3895 of 10000, 38.95 percent done
Working on 1347 of 10000, 13.47 percent done

也就是说,每个模拟运行的情况都会显示在一行中。但我不确定这是否可能,至少我无法想象任何方法来实现这一点。

另一种方法是像这样做:

Sim 1: 1.27%    Sim 2: 2.59%    Sim 3: 38.95%   Sim 4: 13.47%

也就是说,将所有进程都显示在同一行上。为了做到这一点,您需要跟踪每个模拟要写入的行上的位置,并在那里写入,而不删除其他进度。我无法想象这该如何实现,这种做法可行吗?

如果有其他我没有想到的解决我的问题的方法(显示每个单独迭代的进度),我很乐意听听。

由于这是我第一次在SO上提问,很可能有我忽略的问题;如果有的话,请随时在下面评论。

编辑

收到这个答案后,我认为我应该分享一下我如何使用它来解决我的问题,因为我没有像答案中那样完全使用它,以防别人遇到相同的问题。

这是一个小测试程序,基本上与我的程序结构相同,利用了答案中提到的进度条(parfor_progress):

function parfor_progress_test()

    cpus = feature('numCores');
    matlabpool('open', cpus);
    cleaner = onCleanup(@mycleaner);

    args = [1000, 1000, 1000, 1000];
    m = sum(args);
    parfor_progress(m);

    parfor ii = 1:size(args,2)
        my_fun(args(ii));
    end
    parfor_progress(0);

end

function my_fun(N)
    for ii = 1:N
        pause(rand*0.01);
        parfor_progress;
    end
end

function mycleaner
    matlabpool close;
    fclose all;
end

以下是一些非常糟糕的代码,我永远不会在答案中使用它:if mod(ii, 100) == 0; disp(' X ');end。如果我有1000次迭代,如果它已经打印了五个X,则完成了一半——显然这需要一点运气,因为迭代是随机选择的。所以,这是一个完全的hack,但在紧急情况下它确实有效。坦率地说,在实践中,我发现它可以工作,尽管它有明显的缺陷。 - eric
4个回答

19

简单的进度条

类似进度条的东西可以像这样实现...

parfor 循环之前:

fprintf('Progress:\n');
fprintf(['\n' repmat('.',1,m) '\n\n']);

循环期间:

fprintf('\b|\n');

在这里,m 是迭代的总次数,. 显示迭代的总数,| 显示已完成的迭代次数。 \n 确保字符在 parfor 循环中被打印。

进度条和百分比完成

否则,您可以尝试此方法:http://www.mathworks.com/matlabcentral/fileexchange/32101-progress-monitor--progress-bar--that-works-with-parfor
它将显示进度条和百分比完成,但可以很容易地修改为仅包括百分比完成或进度条。
此函数在每个迭代中向文件添加一个字符,然后读取写入该文件的字符数,以指示已完成的迭代次数。在 parfor 中允许使用此文件访问方法。
假设您正确地将上述内容添加到 MATLAB 路径中,然后可以使用以下内容:
arglist = [arg1, arg2, arg3, arg4];
parfor_progress(size(arglist, 2)); % Set the total number of iterations

parfor ii = 1:size(arglist, 2)
    myfun(arglist(ii));
    parfor_progress; % Increment the progress counter
end
parfor_progress(0); % Reset the progress counter

完成时间和完成百分比

还有一个名为showTimeToCompletion()的函数,可以从以下网址获取:https://www.soundzones.com/software/sound-zone-tools/

它可以与parfor_progress一起使用。该函数允许您打印包含开始时间、运行时间长度、预计完成时间和完成百分比的循环进度详细摘要,适用于任何类型的循环,尤其是用于\b(退格)字符,以避免命令窗口被文本淹没。虽然严格上来说它不是进度条,但可能更加信息丰富。

在函数文件头部的第三个示例:

fprintf('\t Completion: ');
showTimeToCompletion; startTime=tic;
len=1e2;
p = parfor_progress( len );
parfor i = 1:len
    pause(1);
    p = parfor_progress;
    showTimeToCompletion( p/100, [], [], startTime );
end

输出以下内容到命令窗口:

     Completion: 31.00%
      Remaining: 00:00:23
          Total: 00:00:33
Expected Finish: 3:30:07PM  14-Nov-2017

这在估算正在运行的模拟的完成时间特别有用,特别是对于可能需要数小时或数天才能完成的模拟。


你提供的两种解决方案都很好,唯一的缺点是它们只能显示整个parfor循环的进度,而不能显示每个单独迭代的进度。不过,这确实解决了我的问题,所以我会接受这个答案。谢谢! - martin-bjork
@martin-bjork 你可以利用文件读写的思路来处理多个循环。你可以使用提供的函数作为起点,并将其调整为与嵌套循环或其他方式相适应的形式。(即仍然不同于您在编辑问题中使用的方式) - JacobD
当然,这是一个很好的起点,如果我有额外的时间,我会尝试基于此创建一些东西。但是,目前你的解决方案已经足够满足我的需求了,所以我想现在就直接使用它 :) - martin-bjork
parforprogess不是一个好的解决方案,因为它会严重减缓parfor循环。至于您的第一个解决方案,它运行良好,但在遍历大量文件时不适用。进度条太大了,不能正常工作。 - Ketchup

3
从R2013b开始,您可以使用PARFEVAL异步地评估您的函数,并使客户端显示进度更新。 (显然,这种方法并不像向您的PARFOR循环添加内容那样简单)。 这里有一个示例 PARFEVAL返回的FutureDiary属性会在处理过程中持续更新,因此如果您有少量大型任务,这可能也很有用。

谢谢你的回答,如果你有R2013b,这似乎是一个很好的解决方案。然而,我只有R2013a,所以很遗憾这对我不起作用。 - martin-bjork
1
你能展示一个在parfor循环中的简单进度条示例吗? - Pedro77

1
从R2017a开始,您可以使用parallel.pool.DataQueueafterEach来实现parforwaitbar,如下所示:
if isempty(gcp('nocreate'))
    parpool('local', 3);
end
dq = parallel.pool.DataQueue;
N = 10;
wb = waitbar(0, 'Please wait...');
% Use the waitbar's UserData to track progress
wb.UserData = [0 N];
afterEach(dq, @(varargin) iIncrementWaitbar(wb));
afterEach(dq, @(idx) fprintf('Completed iteration: %d\n', idx));
parfor idx = 1:N
    pause(rand());
    send(dq, idx);
end
close(wb);

function iIncrementWaitbar(wb)
ud = wb.UserData;
ud(1) = ud(1) + 1;
waitbar(ud(1) / ud(2), wb);
wb.UserData = ud;
end

0

在探索@Edric的答案后,我发现在Matlab文档中有一个示例,可以完全实现对pareval循环的等待栏。请检查help FetchNext

N = 100;
for idx = N:-1:1
    % Compute the rank of N magic squares
    F(idx) = parfeval(@rank, 1, magic(idx));
end
% Build a waitbar to track progress
h = waitbar(0, 'Waiting for FevalFutures to complete...');
results = zeros(1, N);
for idx = 1:N
    [completedIdx, thisResult] = fetchNext(F);
    % store the result
    results(completedIdx) = thisResult;
    % update waitbar
    waitbar(idx/N, h, sprintf('Latest result: %d', thisResult));
end
% Clean up
delete(h)

这里没有'parfor'。 - Abhinav
从技术上讲,“parfeval”执行相同的工作。 - Honigmelone

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