exponenta event banner

Улучшиться parfor Работа

Вы можете повысить производительность parfor- петли различными способами. Это включает параллельное создание массивов внутри цикла; профилирование parfor-контуры; массивы нарезки; и оптимизация кода для локальных работников перед запуском в кластере.

Где создавать массивы

При создании большого массива в клиенте перед parfor-loop, и доступ к нему в цикле, вы можете наблюдать медленное выполнение кода. Чтобы повысить производительность, попросите каждого работника MATLAB ® параллельно создавать собственные массивы или их части. Можно сохранить время передачи данных от клиента к работникам, попросив каждого работника создать свою собственную копию этих массивов параллельно внутри цикла. Рассмотрите возможность изменения обычной практики инициализации переменных перед for-луп, избегая ненужного повторения внутри цикла. Можно обнаружить, что параллельное создание массивов внутри цикла повышает производительность.

Повышение производительности зависит от различных факторов, в том числе

  • размер массивов

  • время, необходимое для создания массивов

  • рабочий доступ ко всем массивам или к их части

  • количество итераций цикла, которые выполняет каждый работник

Учитывать все факторы в этом списке, когда вы собираетесь конвертировать for-закольцовывает в parfor-контуры. Дополнительные сведения см. в разделе Преобразование циклов for-Loops в контуры parfor-Loops.

В качестве альтернативы рассмотрим parallel.pool.Constant для установки переменных на работников пула перед циклом. Эти переменные остаются на рабочих после завершения цикла и остаются доступными для нескольких parfor-контуры. Вы можете повысить производительность с помощью parallel.pool.Constant, поскольку данные передаются работникам только один раз.

В этом примере сначала создается набор больших данных D и выполнить parfor-закольцовывание доступа D. Тогда вы используете D для построения parallel.pool.Constant объект, позволяющий повторно использовать данные путем копирования D каждому работнику. Измерить прошедшее время с помощью tic и toc для каждого случая и обратите внимание на разницу.

function constantDemo
    D = rand(1e7, 1);
    tic
    for i = 1:20
        a = 0;
        parfor j = 1:60
            a = a + sum(D);
        end
    end
    toc
    
    tic
    D = parallel.pool.Constant(D);
    for i = 1:20
        b = 0;
        parfor j = 1:60
            b = b + sum(D.Value);
        end
    end
    toc
end
>> constantDemo
Starting parallel pool (parpool) using the 'local' profile ... connected to 4 workers.
Elapsed time is 63.839702 seconds.
Elapsed time is 10.194815 seconds.
Во втором случае данные отправляются только один раз. Вы можете повысить производительность parfor-закольцовывание с помощью parallel.pool.Constant объект.

Профилирование parfor- петли

Можно профилировать parfor-закольцовывание путем измерения времени, прошедшего с помощью tic и toc. Можно также измерить количество данных, переносимых к работникам и от них в параллельном пуле с помощью ticBytes и tocBytes. Обратите внимание, что это отличается от профилирования кода MATLAB в обычном смысле с помощью профилировщика MATLAB, см. раздел Профилирование кода для повышения производительности.

В этом примере вычисляется спектральный радиус матрицы и преобразуется for-закольцовывание в parfor-луп. Измерьте результирующее ускорение и объем передаваемых данных.

  1. В редакторе MATLAB введите следующее: for-луп. Добавить tic и toc для измерения прошедшего времени. Сохранить файл как MyForLoop.m.

    function a = MyForLoop(A)
        tic
        for i = 1:200
            a(i) = max(abs(eig(rand(A))));
        end
        toc
    end
  2. Запустите код и запишите истекшее время.

    a = MyForLoop(500);
    Elapsed time is 31.935373 seconds.

  3. В MyForLoop.m, замените for-закольцовывание с помощью parfor-луп. Добавить ticBytes и tocBytes для измерения объема данных, переносимых к работникам и от них в параллельном пуле. Сохранить файл как MyParforLoop.m.

    ticBytes(gcp);
    parfor i = 1:200
        a(i) = max(abs(eig(rand(A))));
    end
    tocBytes(gcp)

  4. Запустите новый код и запустите его еще раз. Следует отметить, что первый прогон выполняется медленнее, чем второй прогон, поскольку необходимо запустить параллельный пул и сделать код доступным для работников. Обратите внимание на прошедшее время для второго прогона.

    По умолчанию MATLAB автоматически открывает параллельный пул работников на локальном компьютере.

    a = MyParforLoop(500);
    Starting parallel pool (parpool) using the 'local' profile ... connected to 4 workers.
    ...
                 BytesSentToWorkers    BytesReceivedFromWorkers
                 __________________    ________________________
    
        1        15340                  7024                   
        2        13328                  5712                   
        3        13328                  5704                   
        4        13328                  5728                   
        Total    55324                 24168                   
    
    Elapsed time is 10.760068 seconds. 
    Прошедшее время составляет 31,9 секунды в последовательном формате и 10,8 секунды в параллельном, и показывает, что этот код выигрывает от преобразования в parfor-луп.

Срезающие массивы

Если переменная инициализирована перед parfor-loop, затем используется внутри parfor-loop, он должен быть передан каждому работнику MATLAB, оценивающему итерации цикла. Из клиентской рабочей области передаются только переменные, используемые в цикле. Однако если все вхождения переменной индексируются переменной цикла, каждый работник получает только нужную ему часть массива.

В качестве примера сначала выполняется parfor-закольцовывание с использованием разделенной переменной и измерение прошедшего времени.

% Sliced version

M = 100;
N = 1e6;
data = rand(M, N);

tic
parfor idx = 1:M
    out2(idx) = sum(data(idx, :)) ./ N;
end
toc
Elapsed time is 2.261504 seconds.

Теперь предположим, что вы случайно используете ссылку на переменную data вместо N внутри parfor-луп. Проблема здесь в том, что вызов size(data, 2) преобразует разделенную переменную в широковещательную (несрезанную) переменную.

% Accidentally non-sliced version

clear

M = 100;
N = 1e6;
data = rand(M, N);

tic
parfor idx = 1:M
    out2(idx) = sum(data(idx, :)) ./ size(data, 2);
end
toc
Elapsed time is 8.369071 seconds.
Следует отметить, что прошедшее время больше для случайно передаваемой переменной.

В этом случае можно легко избежать использования без среза data, поскольку результат является константой и может быть вычислен вне цикла. Как правило, можно выполнять вычисления, зависящие только от широковещательных данных перед началом цикла, поскольку широковещательные данные не могут быть изменены внутри цикла. В этом случае вычисление является тривиальным и приводит к скалярному результату, что позволяет извлечь вычисления из цикла.

Оптимизация для локальных и кластерных работников

Выполнение кода на локальных рабочих может обеспечить удобство тестирования приложения без использования ресурсов кластера. Однако при использовании местных работников имеются определенные недостатки или ограничения. Поскольку передача данных не происходит по сети, поведение передачи на локальных рабочих может не указывать на то, как она обычно будет происходить по сети.

При работе с локальными работниками, поскольку все рабочие сеансы MATLAB выполняются на одной и той же машине, повышение производительности от parfor-loop относительно времени выполнения. Это может зависеть от многих факторов, включая количество процессоров и ядер. Ключевой момент заключается в том, что кластер может иметь больше ядер, чем локальный компьютер. Если код может быть многопоточным с помощью MATLAB, то единственный способ быстрее - использовать больше ядер для работы над проблемой, используя кластер.

Можно поэкспериментировать, чтобы проверить, быстрее ли создавать массивы перед циклом (как показано слева ниже), а не чтобы каждый работник создавал собственные массивы внутри цикла (как показано справа).

Попробуйте выполнить следующие примеры локального запуска параллельного пула и обратите внимание на разницу во времени выполнения для каждого цикла. Сначала откройте локальный параллельный пул:

parpool('local')

Выполните следующие примеры и повторите выполнение. Следует отметить, что первый прогон для каждого случая выполняется медленнее, чем второй прогон, поскольку необходимо запустить параллельный пул и сделать код доступным для работников. Обратите внимание на прошедшее время, для каждого случая, для второго прогона.

tic;
n = 200;
M = magic(n);
R = rand(n);
parfor i = 1:n
    A(i) = sum(M(i,:).*R(n+1-i,:));
end
toc
tic;
n = 200;
parfor i = 1:n
    M = magic(n);
    R = rand(n);
    A(i) = sum(M(i,:).*R(n+1-i,:));
end
toc

Работая в удаленном кластере, вы можете найти другое поведение, так как работники могут одновременно создавать свои массивы, экономя время передачи. Поэтому код, оптимизированный для локальных работников, может быть не оптимизирован для работников кластера, и наоборот.

См. также

Связанные темы