如何加速MATLAB代碼運行


學習筆記

V1.0 2015/4/17

如何加速MATLAB代碼運行

 

概述

 

本文源於LDPCC的MATLAB代碼,即《CCSDS標准的LDPC編譯碼仿真》。由於代碼的問題,在信息位長度很長(大於10000)情況下,代碼無法正常運行或執行速度很慢。本文將敘述代碼修改過程中的一系列手段,然對其加速原理不做探究

修訂歷史

以下表格展示了本文檔的修訂過程

日期

版本號

修訂內容

2015/04/17

V1.0

初始版本

 

簡介

 

本程序基於MATLAB 2014a 編寫,本文檔中提到的"MATLAB"均指該特定版本MATLAB。代碼運行結果測試機器是T530i i3 3110M 16G @1600MHz。

MATLAB的幫助文檔 - Advancde Software Development - Performance and Memory中提及了一些改善代碼性能的一些手段。比較通用的包括向量的預先分配內存,這一點在編輯器里也會提示。有時候預先分配內存與否和性能關系很大,譬如

tic

x = 0;

for i = 2:1000000

x(i) = x(i-1)+5;

end

toc

tic

x = zeros(1,1000000);

for i = 2:1000000

x(i) = x(i-1)+5;

end

toc

運行結果顯示為"時間已過 0.500985 秒"和"時間已過 0.073622 秒"。另外在聲明變量的時候不使用原有變量,而創建新變量也可以減少運行時間。還有包括選擇'&'和'&&'的差異。

MATLAB還提供了一些改善性能的手段,包括

  • 將長腳本拆開成小段,調用執行;
  • 將大的代碼塊分開為獨立的函數;
  • 將過分復雜的函數或是表達式采用簡單的來代替;
  • 采用函數,而不是腳本;
  • 向量化代碼,采用MATLAB自帶的函數;
  • 采用矩陣的稀疏結構;
  • 運行MATLAB的時候不要在后台運行其他大的程序;
  • 不要重載任何MATLAB的內建函數或數據類型。

 

不得不說上面的技巧有很多是廢話,而其中向量化是最有效的一種方法之一。向量化代碼中有很多常用的函數,包括

函數

功能(暫略)

all

 

any

 

cumsum

 

diff

 

find

 

ind2sub

 

ipermute

 

logical

 

meshgrid

 

ndgrid

 

permute

 

prod

 

repmat

 

reshape

 

shiftdim

 

sort

 

squeeze

 

sub2ind

 

sum

 

 

在代碼撰寫修改過程中,可以多考慮考慮以上函數。    

實例

 

實例來自於《CCSDS標准的LDPC編譯碼仿真》中代碼(實際上有點點差別),代碼優化從以下幾個方面進行

  • 稀疏
  • 類型轉換
  • 向量化

 

稀疏

仿真中的第一個困難在於ccsdscheckmatrix函數在輸入SIZE_M很大的時候,先不說運行時間,直接就爆內存了。(輸入參數4096,2/3)

先分析分析內存的問題,實際上這個函數的最后輸出結果就是一個矩陣,這個矩陣的大小是12288×28672,計算double型的內存占用也就2G左右。但是函數運行過程中產生了很多中間變量沒有清除。當然最后的解決辦法也沒有去管這些東西,由於矩陣H是稀疏矩陣,所以之際采用sparse后,這個運行就沒有任何問題了。

對於矩陣H和H_sparse = spares(H),占用內存如下(當然H要是稀疏的,不然得不償失)

Name     Size         Bytes      Class   Attributes

H       12288x28672   2818572288   double

H_sparse    12288x28672   1736712     double   sparse

也可以對比稀疏矩陣和原始矩陣的運行時間(和稀疏程度有關)

代碼:tic;H*message';toc;

結果:時間已過 0.288934 秒。

代碼:tic;H_sparse*message';toc;

結果:時間已過 0.001210 秒。

類型轉換

MATLAB中的運算符支持多種類型,譬如矩陣乘法中多用double型變量,但如果一個矩陣是邏輯輸入也沒有關系。但運算速度差異較大,譬如

>> Gc_logic = Gc>0;

>> a=randi([0 1],1,16384);

>> tic;b = a*Gc;toc

時間已過 0.107618 秒。

>> tic;b = a*Gc_logic;toc

時間已過 0.503132 秒。

觀測結果類型為double,我們可以大膽推測實際上邏輯型變量在運算過程中先轉化為了double型(邏輯怎么乘呢?)另一個實驗結果是

>> tic;Gc_logic=double(Gc_logic);b = a*Gc_logic;toc

時間已過 0.546412 秒。

這一定程度上證明了我們的假設。所以在運算過程中數據類型是重要的,如果上述乘法出現在循環內,那么實現轉化矩陣類型是必要的。即使只運行一次,那么顯式的轉化矩陣類型(特制新建變量)也有好處。譬如

>> tic;Gt=double(Gc_logic);b = a*Gt;toc

時間已過 0.373506 秒。

通過創建新變量,運行速度些許。

向量化

向量化實際上是原代碼修改中獲益最大的方法,這實際上是因為原先的譯碼程序寫了太多的循環。向量化后運行時間變成了原先的1/40 。當然,原先的代碼通用性強,而向量化這個過程實際上是運用了H的一些結構的。譯碼函數太復雜,此處不做舉例。

此處分析差分調制中的例子(實際上對這個程序沒有什么影響)

原來的代碼是這個樣子的(更新值為其本身和前一個值的異或)

encodeData_extend = [1 encodeData];

for num = 2:length(encodeData_extend)

encodeData_extend(num) = xor(encodeData_extend(num),encodeData_extend(num-1));

end

向量化的結果為(累加模二代替異或)

encodeData1 = [1 encodeData];

encodeData1_sum = cumsum(encodeData1);

encodeData_2 = mod(encodeData1_sum,2);

運行時間分別為

時間已過 0.023424 秒。

時間已過 0.015003 秒。

雖然后者沒有快很多,但這取決於向量的長度,長度大的話會有較大差距。

 

其他

MATLAB中提及的都能對代碼運行速度帶來細微的改進,包括

  • 將長腳本拆開成小段,調用執行;
  • 將大的代碼塊分開為獨立的函數;
  • 將過分復雜的函數或是表達式采用簡單的來代替;
  • 采用函數,而不是腳本;

上述測試腳本(和以上運行條件有差別)

%% 稀疏矩陣測試
M=4096;
theta=[1    1    2    3    1    1    2    3    1    2    3    0    2    3    0    2    3    0    1    3    0    1    3    0    1    2];
fai=[1787    1077    1753    697    1523    5    2035    331    1920    130    4    85    551    15    1780    1960    3    145    1019    691    132    42    393    502    201    1064
1502    602    749    1662    1371    9    131    1884    1268    1784    19    1839    81    2031    76    336    529    74    68    186    905    1751    1516    1285    1597    1712
1887    521    590    1775    1738    2032    2047    85    1572    78    26    298    1177    1950    1806    128    1855    129    269    1614    1467    1533    925    1886    2046    1167
1291    301    1353    1405    997    2032    11    1995    623    73    1839    2003    2019    1841    167    1087    2032    388    1385    885    707    1272    7    1534    1965    588];
A = zeros(M);
B = eye(M);
L = 0:M-1;
for matrixNum = 1:14
    t_k = theta(matrixNum);
    f_4i_M = floor(4*L/M);
    f_k = fai(f_4i_M+1,matrixNum)';
    col_1 = M/4*(mod((t_k+f_4i_M),4)) + ...
        mod((f_k+L),M/4);
    row_col = col_1+1 + L*M;
    C_temp = zeros(M);
    C_temp(ind2sub([M,M],row_col)) = 1;
    C{matrixNum} = sparse(C_temp)';
end
H = [A A A B B+C{1};B+C{8} B+C{7}+C{6} A A B;A B B+C{5} A C{4}+C{3}+C{2}];
H_23 = [A A;B C{11}+C{10}+C{9};C{14}+C{13}+C{12} B];
H=[H_23 H];
H_full = full(H);
whos H H_full
%% 稀疏矩陣乘法測試
message = randi([0 1],1,28672);
tic;H*message';toc;
tic;H_full*message';toc;

%% 數據類型測試
Gc = randn(16384);
Gc_logic = Gc>0; 
a=randi([0 1],1,16384); 
tic;b = a*Gc;toc 
tic;b = a*Gc_logic;toc %邏輯型運行花費時間
tic;Gc_logic=double(Gc_logic);b = a*Gc_logic;toc %類型轉換
tic;Gt=double(Gc_logic);b = a*Gt;toc %建立新變量

%% 向量化測試
encodeData = randi([0 1],1,1000000);

tic;
encodeData_extend = [1 encodeData];
for num = 2:length(encodeData_extend)
    encodeData_extend(num) = xor(encodeData_extend(num),encodeData_extend(num-1));
end
toc;
tic;
encodeData1 = [1 encodeData];
encodeData1_sum = cumsum(encodeData1);
encodeData_2 = mod(encodeData1_sum,2);
toc;
View Code

 

profile

 

上一小節內容中有一句"實際上對這個程序沒有什么影響",我們怎么判斷哪些代碼要修改,哪些代碼即使修改得再好對整個代碼運行也沒有什么影響呢?三種方法

  • 感覺(不可靠)
  • tic;toc;(太麻煩)
  • profile工具(很不錯)

profile的功能可以help以下如何使用,我沒怎么看,所以不怎么會用……profile是用來分析代碼各個語句的運行時間的工具。使用方法是

  1. 輸入profile on
  2. 運行需要測試的代碼
  3. 輸入profile viewer

結果如下圖

點擊各個函數(腳本)可以仔細觀測各個語句的運行狀態,由此來幫助優化MATLAB代碼,就像這樣

反正挺不錯的,但我不太會用就不多說了。

參考

 

MATLAB幫助

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM