Улучшайте производительность parfor

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

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

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

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

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

  • время должно было создать массивы

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

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

Полагайте, что все включает этот список, когда вы рассматриваете, чтобы преобразовать for - циклы к parfor - циклы. Для получения дополнительной информации смотрите, Преобразовывают циклы for В циклы 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
>> 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, смотрите Профиль, чтобы Улучшать Производительность (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
    
  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 - цикл, то используемый в parfor - цикл, это должно быть передано каждому работнику 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

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

Смотрите также

Похожие темы