Руководство по оптимизации кода MATLAB

Я заметил много отдельных вопросов по SO, но ни одного хорошего руководства по оптимизации MATLAB.

Популярные Вопросы:

  • оптимизировать этот код для меня
  • как я могу векторизовать это?

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

оптимизация кода Matlab-это своего рода черное искусство, всегда есть лучший способ сделать это. И иногда совершенно невозможно векторизовать свой код.

Итак, мой вопрос: когда векторизация невозможна или чрезвычайно сложна, каковы некоторые из ваших советов и трюков для оптимизации кода MATLAB? Кроме того, если у вас есть какие-либо общие трюки векторизации, я бы тоже не прочь их увидеть.

1 ответов


предисловие

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

пожалуйста, не обращайте внимания на отдельные цифры, достаточно посмотреть на различия между до и после оптимизации.

Примечание: на tic и toc вызовы, которые я разместил в коде, должны показать, где я нахожусь измерение затраченного времени.

предварительное выделение

простой акт предварительного выделения массивов в MATLAB может дать огромное преимущество в скорости.

tic;


for i = 1:100000
    my_array(i) = 5 * i;
end

toc;

это нужно 47 секунд

tic;

length = 100000;
my_array = zeros(1, length);

for i = 1:length
    my_array(i) = 5 * i;
end

toc;

это нужно 0.1018 секунд

47 секунд до 0,1 секунды для одной строки кода добавил удивительное улучшение. Очевидно, что в этом простом примере вы можете векторизовать его my_array = 5 * 1:100000 (который взял 0.000423 секунд), но я пытаюсь представить более сложные времена, когда векторизация не является вариантом.

недавно я обнаружил, что функция нулей (и другие такого же характера) не так быстры при предварительном выделении, как просто установка последнего значения в 0:

tic;

length = 100000;
my_array(length) = 0;

for i = 1:length
    my_array(i) = 5 * i;
end

toc;

это нужно 0.0991 секунд

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

почему это работает?

методы предварительного выделения выделяют кусок памяти для работы. Эта память непрерывна и может быть предварительно извлечена, как массив в C++ или Java. Однако, если вы не предварительно выделяете, MATLAB придется динамически находить все больше и больше памяти для использования. Как я понимаю, это ведет себя в отличие от Java ArrayList и больше похож на LinkedList, где различные куски массива разбиты по всему месту в памяти.

это не только медленнее, когда вы пишете данные на него (47 секунд!) но это также медленнее каждый раз, когда вы получаете доступ к нему с тех пор. На самом деле, если вы абсолютно не можете предварительно выделить, все равно полезно скопировать матрицу в новую предварительно выделенную, прежде чем начать ее использовать.

что делать, если я не знаю, сколько места выделить?

это общий вопрос и есть несколько различных решений:

  1. переоценка - лучше сильно переоценивать размер вашей матрицы и выделять слишком много места, чем недооценивать пространство.
  2. разобраться с этим и исправить позже - я видел это много, где разработчик смирился с медленным временем заполнения, а затем скопировал матрицу в новый предварительно выделенный пространство. Обычно это сохраняется как .mat файл или аналогичный, чтобы его можно было быстро прочитать позже.

как предварительно выделить сложную структуру?

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

я никогда не мог бы явно предварительно выделить их (я надеюсь, что кто-то может предложить лучший метод), поэтому Я придумал этот простой хак:

tic;

length = 100000;

% Reverse the for-loop to start from the last element
for i = 1:length
    complicated_structure = read_from_file(i);
end

toc;

это нужно 1.5 минут

tic;

length = 100000;

% Reverse the for-loop to start from the last element
for i = length:-1:1
    complicated_structure = read_from_file(i);
end

% Flip the array back to the right way
complicated_structure = fliplr(complicated_structure);

toc;

это нужно 6 секунд

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

сведения Структуры

С точки зрения использования памяти массив структур на порядок хуже, чем структура массивов:

% Array of Structs
a(1).a = 1;
a(1).b = 2;
a(2).a = 3;
a(2).b = 4;

использует 624 байт

% Struct of Arrays
a.a(1) = 1;
a.b(1) = 2;
a.a(2) = 3;
a.b(2) = 4;

использует 384 байт

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

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

Читает

меньше freads (или любой системный вызов, если на то пошло) у вас в коде, тем лучше.

tic;    

for i = 1:100
    fread(fid, 1, '*int32');
end

toc;

предыдущий код намного медленнее, чем следующий:

tic;
fread(fid, 100, '*int32');
toc;

можно подумать, что это очевидно, но тот же принцип может быть применен к более сложным случаям:

tic;

for i = 1:100
    val1(i) = fread(fid, 1, '*float32');
    val2(i) = fread(fid, 1, '*float32');
end

toc;

эта проблема больше не проста, потому что в памяти поплавки представлены следующим образом:

val1 val2 val1 val2 etc.

однако вы можете использовать skip значение fread для достижения тех же оптимизаций, что и раньше:

tic;

% Get the current position in the file
initial_position = ftell(fid);

% Read 100 float32 values, and skip 4 bytes after each one
val1 = fread(fid, 100, '*float32', 4);

% Set the file position back to the start (plus the size of the initial float32)
fseek(fid, position + 4, 'bof');

% Read 100 float32 values, and skip 4 bytes after each one
val2 = fread(fid, 100, '*float32', 4);

toc;

таким образом, чтение этого файла было выполнено с использованием двух freads вместо 200, массивный улучшение.

Звонки

недавно я работал над кодом, который использовали многие вызовы функций, все из которых были расположены в отдельных файлах. Итак, скажем, было 100 отдельных файлов, все звонили друг другу. "Встраивая" этот код в одну функцию, я увидел улучшение скорости выполнения на 20% от 9 секунд.

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

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

Построение Многих Отключенных Линий

при построении несвязанных данных, таких как набор вертикальных линий, традиционным способом сделать это в Matlab является итерация нескольких звонки в line или plot используя hold on. Однако если у вас есть большое количество отдельных линий сюжета, это становится очень медленным.

техника я нашел использует тот факт, что вы можете ввести NaN значения в данные для построения, и это вызовет перерыв в данных.

приведенный ниже надуманный пример преобразует набор x_values, y1_values и y2_values (где строка находится от [x, y1] до [x, y2]) в формат, подходящий для одного вызова к plot.

например:

% Where x is 1:1000, draw vertical lines from 5 to 10.
x_values = 1:1000;
y1_values = ones(1, 1000) * 5;
y2_values = ones(1, 1000) * 10;

% Set x_plot_values to [1, 1, NaN, 2, 2, NaN, ...];
x_plot_values = zeros(1, length(x_values) * 3);
x_plot_values(1:3:end) = x_values;
x_plot_values(2:3:end) = x_values;
x_plot_values(3:3:end) = NaN;

% Set y_plot_values to [5, 10, NaN, 5, 10, NaN, ...];
y_plot_values = zeros(1, length(x_values) * 3);
y_plot_values(1:3:end) = y1_values;
y_plot_values(2:3:end) = y2_values;
y_plot_values(3:3:end) = NaN;

figure; plot(x_plot_values, y_plot_values);

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