Параллельные вычисления в MatLab

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

В статье рассматривается возможность параллельных вычислений в среде MatLab и Simulink, с помощью пакета Parallel Computing Toolbox. 

MatLab и Simulink предоставляет большое количество великолепных инструмент для математических вычислений. Однако, не всегда эти вычисления происходят быстро. Для ускорения вычислений в MatLab есть такой инструмент, как Parallel Computing Toolbox, который позволяет производить параллельные вычисления.

1. Параллельное вычисление в цикле for (parfor)

Разберем чем отличается parfor от for. Если в том, как работает цикл for проблем не возникает, то с parfor есть вопросы. К примеру, действия, в отличии от стандартного цикла for, в parfor итерации цикла могут производиться не последовательно. Например, может быть ситуация, где тело цикла с индексом i = 205 может идти раньше, чем i = 160. Это возникает из-за разности производительности вычислительных потоков. Из-за этого же, так как задачи выполняются в разных пулах, невозможно воспользоваться значениями и результатами вычисления из предыдущих итераций.

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

n = 15;
tic;
for i = 1:500
    a = fibonacci(n);
end
time = toc

На тестовой машине вычисление 15 число Фибоначчи 500 раз и работает занимает 7.67 секунд.

Для использования параллельных вычислений необходимо заменить for на parfor, а также запустить второй (или по числу желаемых потоков) pool для вычислений.

n = 15;
tic;
parfor i = 1:500
    a = fibonacci(n);
end
time = toc

При первом запуске он включится автоматически, но это займет время. В случае если второй pool для вычислений уже запущен, то время на вычисление данной функции сокращается до 5.06 секунд. Ускорение расчета составляет примерно 35%, что значительно меньше ожидаемого результата при удвоении вычислительных ресурсов.

Рассмотрим другой процесс вычислений, который является более долгим:

tic;
for i = 1:10
    a = rand(3000, 3000);
    a = tf(a);
end
time = toc

и занимает 56.3 секунды в однопоточном режиме.

Заменим for на parfor с использованием двух вычислительных потоков

tic;
parfor i = 1:10
    a = rand(3000, 3000);
    a = tf(a);
end
time = toc

Время выполнения сокращается до 34 секунд, что соответствует повышению производительности на 65%.

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

2. Параллельное вычисление Simulink

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

Обычно для вычисления и получения данных из модели Simulink используется команда sim. Аналогично, для параллельного вычисления можно использовать команду parsim и специальный параметр SimulationInput, передаваемый в эту функцию.  Которая запускает Simulink модель с определенными в этом объекте параметрами.

Рассмотрим на примере следующей модели:

Для того чтобы задать параметры мы необходимо определиться с количеством экспериментов:

Cf_sweep = Cf*(0.05:0.1:0.95);
numSims = length(Cf_sweep);

Затем определить наши входные параметры

for i = numSims:-1:1
    in(i) = Simulink.SimulationInput(mdl);
    in(i) = setBlockParameter(in(i), ['TestModel' '/Road-Suspension Interaction'], 'Cf', num2str(Cf_sweep(i)));
end

Для запуска вычислений модели выполним следующую команду:

out = parsim(in, 'ShowProgress', 'on');

В поле вывода MatLab можно будет увидеть следующую информацию:

Результат параллельного моделирования Simulink

Основная суть которой в том, что у нас вычисления совершались параллельно и завершились.

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

for i = numSims:-1:1
        simOut = out(i);
        ts = simOut.logsout.get('vertical_disp').Values;
        plot(ts);
end

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

В качестве рассматриваемой модели возьмем следующую:

Simulink модели для параллельных вычислений

Изначально скрипт построения фазовых портретов выглядел следующим образом:

hold on
for y0=-5:1:5
    for x0=-5:1:5 
       sim('fazPort')
       plot(x, y)
    end
end
hold off

Для возможности параллельного моделирования необходимо изменить модель: необходимо включить логирование данные, для дальнейшей обработки. Для этого заменяются блоки to workspace на блок out и ставится параметр log this signal для интересующих нас сигналов.

Кроме того скрипт запуска моделирования требует изменений:

% Генерируем массив начальных условий для системы
[p,q] = meshgrid(-2:2, -5:5);
pairs = [p(:) q(:)];
 
% Генерируем массив параметров параллельного моделирования
for i = 55:-1:1
    in(i) = Simulink.SimulationInput('fazPort');
    in(i) = in(i).setVariable('y0',pairs(i,1));
    in(i) = in(i).setVariable('x0',pairs(i,2));
end

% Запускаем параллельное моделирование
out = parsim(in, 'ShowSimulationManager', 'on')
 
% Производим построения фазового портрета
figure(2)
hold on
for i = 1:55
    simOut = out(i);
    x = simOut.logsout.get(1).Values.Data;
    y = simOut.logsout.get(2).Values.Data;
    plot(x, y)
end

На выходе получаем точно такой же фазовый портрет, но вычисленный в 2 потока. Так же, дополнительное окно SimulationManager показывает прогресс вычислений. В нем можно увидеть входные и выходные данные для каждого запуска.