MATLAB 效率再議 zz

2021-06-01 00:41:44 字數 4303 閱讀 9414

好文轉貼自

關 於matlab的效率問題,很多文章,包括我之前寫的一些,主要集中在使用向量化以及相關的問題上。但是,最近我在實驗時對**進行profile的過程 中,發現在新版本的matlab下,for-loop已經得到了極大優化,而效率的瓶頸更多是在函式呼叫和索引訪問的過程中。

由於matlab特有的解釋過程,不同方式的函式呼叫和元素索引,其效率差別巨大。不恰當的使用方式可能在本來不起眼的地方帶來嚴重的開銷,甚至可能使你的**的執行時間增加上千倍(這就不是多買幾台伺服器或者增加計算節點能解決的了,呵呵)。

下 面通過一些簡單例子說明問題。(實驗選在裝有windows vista的一台普通的台式電腦(core2 duo 2.33ghz + 4gb ram)進行,相比於計算集群, 這可能和大部分朋友的環境更相似一些。實驗過程是對某乙個過程實施多次的整體進行計時,然後得到每次過程的平均時間,以減少計時誤差帶來的影響。多次實 驗,在均值附近正負20%的範圍內的置信度高於95%。為了避免算上首次執行時花在預編譯上的時間,在開始計時前都進行充分的「熱身」執行。)

函式呼叫的效率

乙個非常簡單的例子,把向量中每個元素加1。(當然這個例子根本不需要調函式,但是,用它主要是為了減少函式執行本身的時間,突出函式解析和呼叫的過程。)

作為baseline,先看看最直接的實現

% input u: u is a 1000000 x 1 vector

v = u + 1;

這個過程平均需要0.00105 sec。而使用長期被要求盡量避免的for-loop

n = numel(u);

% v = zeros(n, 1) has been pre-allocated.

for i = 1 : n

v(i) = u(i) + 1;

end

所需的平均時間大概是0.00110 sec。從統計意義上說,和vectorization已經沒有顯著差別。無論是for-loop或者vectorization,每秒平均進行約10億次「索引-讀取-加法-寫入」的過程,計算資源應該得到了比較充分的利用。

要是這個過程使用了函式呼叫呢?matlab裡面支援很多種函式呼叫方式,主要的有m-function, function handle, anonymous function, inline, 和feval,而feval的主引數可以是字串名字,function handle, anonymous function或者inline。

用m-function,就是專門定義乙個函式

function y = fm(x)

y = x + 1;

在呼叫時
for i = 1 : n

v(i) = fm(u(i));

end

function handle就是用@來把乙個function賦到乙個變數上,類似於c/c++的函式指標,或者c#裡面的delegate的作用

fh = @fm;

for i = 1 : n

v(i) = fh(u(i));

end

anonymous function是一種便捷的語法來構造簡單的函式,類似於lisp, python的lambda表示式

fa = @(x) x + 1;

for i = 1 : n

v(i) = fa(u(i));

end

inline function是一種傳統的通過表示式字串建構函式的過程

fi = inline('x + 1', 'x');

for i = 1 : n

v(i) = fi(u(i));

end

feval的好處在於可以以字串方式指定名字來呼叫函式,當然它也可以接受別的引數。

v(i) = feval('fm', u(i));

v(i) = feval(fh, u(i));

v(i) = feval(fa, u(i));

對於100萬次呼叫(包含for-loop本身的開銷,函式解析(resolution),壓棧,執行加法,退棧,把返回值賦給接收變數),不同的方式的時間差別很大:

m-function

0.385 sec

function handle

0.615 sec

anonymous function

0.635 sec

inline function

166.00 sec

feval('fm', u(i))

8.328 sec

feval(fh, u(i))

0.618 sec

feval(fa, u(i))

0.652 sec

feval(@fm, u(i))

2.788 sec

feval(@fa, u(i))

34.689 sec

從這裡面,我們可以看到幾個有意思的現象:

在2023年以後,matlab推出了arrayfun函式,上面的for-loop可以寫為

v = arrayfun(fh, u)
這平均需要4.48 sec,這比起for-loop(需時0.615 sec)還慢了7倍多。這個看上去「消除了for-loop"的函式,由於其內部設計的原因,未必能帶來效率上的正效果。

元素和域的訪問

除了函式呼叫,資料的訪問方式對於效率也有很大影響。matlab主要支援下面一些形式的訪問:

這裡主要探索單個元素或者域的訪問(當然,matlab也支援對於子陣列的非常靈活整體索引)。

對於一百萬次訪問的平均時間

a(i) for a numeric array

0.0052 sec

c for a cell array

0.2568 sec

struct field

0.0045 sec

struct field (with dynamic name)

1.0394 sec

我們可以看到matlab對於單個陣列元素或者靜態的struct field的訪問,可以達到不錯的速度,在主流台式電腦約每秒2億次(連同for-loop的開銷)。而cell array的訪問則明顯緩慢,約每秒400萬次(慢了50倍)。matlab還支援靈活的使用字串來指定要訪問域的語法(動態名字),但是,是以巨大的 開銷為代價的,比起靜態的訪問慢了200倍以上。

關於object-oriented programming

matlab在新的版本中(尤其是2008版),對於物件導向的程式設計提供了強大的支援。在2008a中,它對於oo的支援已經不亞於 python等的高階指令碼語言。但是,我在實驗中看到,雖然在語法上提供了全面的支援,但是matlab裡面物件導向的效率很低,開銷巨大。這裡僅舉幾個 例子。

建議 根據上面的分析,對於撰寫高效matlab**,我有下面一些建議:

雖然for-loop的速度有了很大改善,vectorization(向量化)仍舊是改善效率的重要途徑,尤其是在能把運算改寫成矩陣乘法的情況下,改善尤為顯著。

在不少情況下,for-loop本身已經不構成太大問題,尤其是當迴圈體本身需要較多的計算的時候。這個時候,改善概率的關鍵在於改善迴圈體本身而不是去掉for-loop。

matlab的函式呼叫過程(非built-in function)有顯著開銷,因此,在效率要求較高的**中,應該盡可能採用扁平的呼叫結構,也就是在保持**清晰和可維護的情況下,盡量直接寫表示式 和利用built-in function,避免不必要的自定義函式呼叫過程。在次數很多的迴圈體內(包括在cellfun, arrayfun等實際上蘊含迴圈的函式)形成長呼叫鏈,會帶來很大的開銷。

在呼叫函式時,首選built-in function,然後是普通的m-file函式,然後才是function handle或者anonymous function。在使用function handle或者anonymous function作為引數傳遞時,如果該函式被呼叫多次,最好先用乙個變數接住,再傳入該變數。這樣,可以有效避免重複的解析過程。

在可能的情況下,使用numeric array或者struct array,它們的效率大幅度高於cell array(幾十倍甚至更多)。對於struct,盡可能使用普通的域(字段,field)訪問方式,在非效率關鍵,執行次數較少,而靈活性要求較高的代 碼中,可以考慮使用動態名稱的域訪問。

雖然object-oriented從軟體工程的角度更為優勝,而且object的使用很多時候很方便,但是matlab目前對於oo的實現效率很低,在效率關鍵的**中應該慎用objects。

如果需要設計類,應該盡可能採用普通的property,而避免靈活但是效率很低的dependent property。如非確實必要,避免過載subsref和subsasgn函式,因為這會全面接管對於object的介面呼叫,往往會帶來非常巨大的開 銷(成千上萬倍的減慢),甚至使得本來幾乎不是問題的**成為效能瓶頸。

Matlab撿知識 S Function再體驗

作為乙個學控制 越來越偏離軌道 的學生,使用simulink的小部分功能是在頻繁不過的了,但是我本科到碩士一年級,用simulink呼叫自建函式,不是簡單s函式就是複雜m function,基本都會避開s function 一開始感覺賊麻煩 其實,m function和s function同時用到複...

Matlab向量化再探

之前的觀點是matlab的向量化的計算效率比for迴圈高。今天考察乙個例子,計算10000個隨機數相加,平台是matlab 2018b。先說結論 進行預定義矩陣尺寸的for迴圈並且減少迴圈中的重複計算的情況下,效率比自帶函式的效率要高。從此看出,提高效率的關鍵在於定義矩陣尺寸,而向量化不能有效的提高...

MATLAB入門(MATLAB命令)

管理會話中的命令 clc 刪除命令視窗 clear 刪除記憶體中的變數 exist 檢查變數是否存在 global 宣告變數為全域性變數 help 獲取幫助資訊 1 查詢各種算術運算子 關係運算子 邏輯運算子 2 查詢名稱準確已知的命令或檔案 3 非matlab自帶.m檔案的幫助性注釋內容 look...