[英]Guide to Optimizing MATLAB Code
我已經注意到很多關於SO的問題,但沒有一個很好的MATLAB優化指南。
常見問題:
我不認為這些問題會停止,但我希望這里提出的想法能讓他們集中參考。
優化Matlab代碼是一種黑色藝術,總有一種更好的方法。 有時甚至無法對代碼進行矢量化。
所以我的問題是:當矢量化不可能或極其復雜時,優化MATLAB代碼的一些技巧和竅門是什么? 此外,如果你有任何常見的矢量化技巧,我也不介意看到它們。
所有這些測試都是在與他人共享的機器上執行的,因此它不是一個非常干凈的環境。 在每次測試之間,我清除工作區以釋放內存。
請不要注意個別數字,只需看看優化時間之前和之后的差異。
注意:我在代碼中放置的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秒!),而且每次從那里開始訪問它時速度也會變慢。 實際上,如果您絕對無法預先分配,那么在開始使用之前將矩陣復制到新的預分配仍然很有用。
如果我不知道要分配多少空間怎么辦?
這是一個常見問題,有幾種不同的解決方案:
.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字節
正如您所看到的,即使在這個簡單/小的示例中,結構數組也比結構數組使用更多的內存。 如果要繪制數據,陣列結構也是一種更有用的格式。
每個Struct都有一個大的頭,你可以看到一個結構數組重復多次這個頭,其中數組的結構只有一個頭,因此占用的空間更少。 對於較大的陣列,這種差異更明顯。
您在代碼中擁有的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.
但是,您可以使用fread的skip
值來實現與以前相同的優化:
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;
所以這個文件讀取是使用兩個fread
而不是200來完成的,這是一個巨大的進步。
我最近研究了一些使用了許多函數調用的代碼,所有函數調用都位於不同的文件中。 所以我們說有100個單獨的文件,都互相調用。 通過將此代碼“內聯”到一個函數中,我看到執行速度從9秒提高了20%。
顯然你不會以犧牲可重用性為代價來做到這一點,但在我的情況下,這些函數是自動生成的,根本不會被重用。 但我們仍然可以從中吸取教訓,避免在不需要的情況下進行過多的函數調用。
外部MEX功能會產生被調用的開銷。 因此,對大型MEX函數的調用比對較小MEX函數的許多調用要高效得多。
在繪制斷開連接的數據(如一組垂直線)時,在Matlab中執行此操作的傳統方法是使用hold on
迭代多次調用line
或plot
。 但是,如果要繪制大量單獨的線條,則會變得非常慢。
我發現的技術使用了這樣一個事實:你可以將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);
我已經使用這種方法來打印數千條細線,性能改進非常大。 不僅在初始繪圖中,而且后續操作(如縮放或平移操作)的性能也得到了改善。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.