Matlab: прогресс печати из цикла parfor

Я запускаю много длительных симуляций в 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 есть только 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

Ответ 1

Простой индикатор выполнения

Что-то вроде индикатора выполнения может быть сделано аналогично этому...

Перед циклом 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. Эта функция позволяет вам распечатать подробную сводку о прогрессе цикла parfor (или любого цикла в этом отношении), который содержит время начала, продолжительность работы, расчетное время окончания и процентное завершение. Он использует интеллектуальное использование символа \b (backspace), чтобы окно команды не заливалось текстом. Хотя это не просто "бар" прогресса, возможно, более информативный.

Третий пример в заголовке файла функции,

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

Полезно для оценки завершения текущей симуляции, особенно той, которая может занимать часы или дни.

Ответ 2

Начиная с R2013b, вы можете использовать PARFEVAL для асинхронной оценки своей функции и обновления отображения хода клиента. (Очевидно, что этот подход не так прост, как добавление материала в цикл PARFOR). Здесь приведен пример здесь.

Свойство Diary Future, возвращаемое PARFEVAL, постоянно обновляется во время обработки, поэтому может быть полезно, если у вас есть небольшое количество больших задач.

Ответ 3

после изучения ответа @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)

Ответ 4

Начиная с R2017a, вы можете использовать parallel.pool.DataQueue и afterEach, чтобы реализовать waitbar для parfor, например:

if isempty(gcp('nocreate'))
    parpool('local', 3);
end
dq = parallel.pool.DataQueue;
N = 10;
wb = waitbar(0, 'Please wait...');
% Use the waitbar 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