Оптимизация скорости MATLAB

102
13

Может ли кто-нибудь помочь? Я довольно опытный пользователь Matlab, но мне не удается ускорить выполнение кода ниже.


Самое быстрое время, которое я смог достичь за один проход через все три петли, используя 12 ядер, составляет ~ 200 с. Фактическая функция будет называться ~ 720 раз, и при этом скорость займет более 40 часов. Согласно профилировщику Matlab, большая часть времени процессора тратится на вызов экспоненциальной функции. Мне удалось значительно ускорить это, используя gpuArray, а затем запустить вызов exp на видеокарте Quadro 4000, однако это предотвращает использование цикла parfor, поскольку на рабочей станции есть только одна видеокарта, которая уничтожает любую прибыль. Может ли кто-нибудь помочь, или этот код близок к оптимальному, который может быть достигнут с помощью Matlab? Я написал очень грубую реализацию С++ с помощью openMP, но получил небольшой выигрыш.


Большое спасибо заранее


function SPEEDtest_CPU

% Variable setup:
% - For testing I'll use random variables. These will actually be fed into
% the function for the real version of this code.
sy = 320;
sx = 100;
sz = 32;
A = complex(rand(sy,sx,sz),rand(sy,sx,sz));
B = complex(rand(sy,sx,sz),rand(sy,sx,sz));
C = rand(sy,sx);
D = rand(sy*sx,1);
F = zeros(sy,sx,sz);
x = rand(sy*sx,1);
y = rand(sy*sx,1);
x_ind = (1:sx) - (sx / 2) - 1;
y_ind = (1:sy) - (sy / 2) - 1;

% MAIN LOOPS
% - In the real code this set of three loops will be called ~720 times!
% - Using 12 cores, the fastest I have managed is ~200 seconds for one
% call of this function.
tic
for z = 1 : sz
A_slice = A(:,:,z);
A_slice = A_slice(:);
parfor cx = 1 : sx
for cy = 1 : sy
E = ( x .* x_ind(cx) ) + ( y .* y_ind(cy) ) + ( C(cy,cx) .* D );

F(cy,cx,z) = (B(cy,cx,z) .* exp(-1i .* E))' * A_slice;
end
end
end
toc

end

спросил(а) 2013-10-04T13:09:00+04:00 6 лет, 9 месяцев назад
1
Решение
78

Некоторые вещи, о которых нужно подумать:


Рассматривали ли вы использование синглов?

Можно ли вы векторизовать часть cx, cy, чтобы они представляли операции массива?


Рассмотрите возможность изменения режимов округления или сигнализации с плавающей запятой.

ответил(а) 2013-10-04T13:13:00+04:00 6 лет, 9 месяцев назад
66

Если ваши данные являются реальными (не сложными), как в вашем примере, вы можете сэкономить время на замену


(B(cy,cx,z) .* exp(-1i .* E))'

по


(B(cy,cx,z) .* (cos(E)+1i*sin(E))).'

В частности, на моей машине (cos(x)+1i*sin(x)).' занимает 19% меньше времени, чем exp(-1i .* x)'.

Если A и B являются сложными: E по-прежнему велик, поэтому вы можете прекомпостировать Bconj = conj(B) вне циклов (это занимает около 10 мс с размером данных, и это выполняется только один раз), а затем заменить


(B(cy,cx,z) .* exp(-1i .* E))'

по


(Bconj(cy,cx,z) .* (cos(E)+1i*sin(E))).'

чтобы получить аналогичный коэффициент усиления.

ответил(а) 2013-10-04T13:28:00+04:00 6 лет, 9 месяцев назад
54

Вы можете переместить x .* x_ind(cx) из самого внутреннего цикла. У меня нет GPU для тестирования таймингов, но вы можете разбить код на три раздела, чтобы вы могли использовать графический процессор и паркур


for z = 1 : sz
E = zeros(sy*sx,sx,sy);
A_slice = A(:,:,z);
A_slice = A_slice(:);
parfor cx = 1 : sx
temp = ( x .* x_ind(cx) );
for cy = 1 : sy
E(:, cx, cy) = temp + ( y .* y_ind(cy) ) + ( C(cy,cx) .* D );
end
end
temp = zeros(zeros(sy*sx,sx,sy));
for cx = 1 : sx
for cy = 1 : sy
% Ideally use your GPU magic here
temp(:, cx, cy) = exp(-1i .* E(:, cx, cy)));
end
end
parfor cx = 1 : sx
for cy = 1 : sy
F(cy,cx,z) = (B(cy,cx,z) .* temp(:, cx, cy)' * A_slice;
end
end
end

ответил(а) 2013-10-04T14:16:00+04:00 6 лет, 9 месяцев назад
55

Существует два основных способа ускорения кода MATLAB; preallocation и vectorisation.


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


Векторизация может привести к ускорению на несколько порядков и оптимально использовать ядра (при условии, что флаг поднят).


Какое математическое выражение вы вычисляете, и я могу дать руку?

ответил(а) 2013-10-04T13:21:00+04:00 6 лет, 9 месяцев назад
40

Эта строка: (x. * x_ind (cx)) + (y. * y_ind (cy)) + (C (cy, cx). * D);


- это некоторый тип свертки, не так ли? Круговая свертка намного быстрее в частотной области, и преобразование в/из частотной области оптимизировано с использованием FTT.

ответил(а) 2017-02-04T20:03:00+03:00 3 года, 5 месяцев назад
39

В дополнение к другим хорошим рекомендациям, данным здесь другими, умножение на A_slice не зависит от петель cx,cy и может быть выведено за их пределами, умножая F после завершения обеих циклов.


Аналогично, сопряжение B*exp(...) также может быть выполнено en-bulk вне цикла cx,cy до умножения на A_slice.

ответил(а) 2013-10-10T11:14:00+04:00 6 лет, 8 месяцев назад
39

Не уверен, что это помогает со скоростью - но поскольку E в основном представляет собой сумму, возможно, вы можете использовать это exp (i cx(A+1)x) = exp(i cx(A) x) * exp(i x) и exp(i x), которое может быть рассчитано заранее.


Таким образом, вам не нужно было бы оценивать exp каждую итерацию, но просто нужно умножить, что должно быть быстрее.

ответил(а) 2013-10-04T13:57:00+04:00 6 лет, 9 месяцев назад
39

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


Кроме того, попробуйте как можно больше векторизовать один простой пример: y.*y_ind(cy)


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

ответил(а) 2013-10-04T13:22:00+04:00 6 лет, 9 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема