簡體   English   中英

優化MATLAB代碼指南

[英]Guide to Optimizing MATLAB Code

我已經注意到很多關於SO的問題,但沒有一個很好的MATLAB優化指南。

常見問題:

  • 為我優化此代碼
  • 我該如何對此進行矢量化?

我不認為這些問題會停止,但我希望這里提出的想法能讓他們集中參考。

優化Matlab代碼是一種黑色藝術,總有一種更好的方法。 有時甚至無法對代碼進行矢量化。

所以我的問題是:當矢量化不可能或極其復雜時,優化MATLAB代碼的一些技巧和竅門是什么? 此外,如果你有任何常見的矢量化技巧,我也不介意看到它們。

前言

所有這些測試都是在與他人共享的機器上執行的,因此它不是一個非常干凈的環境。 在每次測試之間,我清除工作區以釋放內存。

請不要注意個別數字,只需看看優化時間之前和之后的差異。

注意:我在代碼中放置的tictoc調用是顯示我在哪里測量所花費的時間。

預分配

在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字節

正如您所看到的,即使在這個簡單/小的示例中,結構數組也比結構數組使用更多的內存。 如果要繪制數據,陣列結構也是一種更有用的格式。

每個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迭代多次調用lineplot 但是,如果要繪制大量單獨的線條,則會變得非常慢。

我發現的技術使用了這樣一個事實:你可以將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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM