Улучшиться parfor Эффективность

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

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

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

Повышение эффективности зависит от различных факторов, включая

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

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

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

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

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

В качестве альтернативы рассмотрим 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- циклы

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

Этот пример вычисляет спектральный радиус матрицы и преобразует for-цикл в a 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-цикл с a 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-цикл, затем используется внутри 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-цикл относительно времени выполнения. Это может зависеть от многих факторов, в том числе от того, сколько процессоров и ядер у вашей машины. Ключевой точкой здесь является то, что кластер может иметь больше ядер, чем ваш локальный компьютер. Если ваш код может быть многопоточен 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

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

См. также

Похожие темы