2013-08-16 71 views
11

在MATLAB中有效學習編程的第一件事就是避免動態調整數組的大小。標準示例如下。在MATLAB中高效的數組預分配

N = 1000; 

% Method 0: Bad 
clear a 
for i=1:N 
    a(i) = cos(i); 
end 

% Method 1: Better 
clear a; a = zeros(N,1); 
for i=1:N 
    a(i) = cos(i) 
end 

「壞」變體在這裏需要O(N^2)的時間來運行,因爲它必須分配一個新的數組,並在循環的每次迭代複製舊值。

我自己在調試時首選的做法是分配一個數組,NaN,很難混淆有效的值比0

% Method 2: Easier to Debug 
clear a; a = NaN(N,1); 
for i=1:N 
    a(i) = cos(i) 
end 

然而,人們會天真地認爲,一旦我們的代碼調試,我們被分配一個數組,然後用0NaN填充它浪費時間。如前所述here,您可以按如下

% Method 3 : Even Better? 
clear a; a(N,1) = 0; 
for i=1:N 
    a(i) = cos(i); 
end 

然而,在我自己的測試(MATLAB R2013a)可能創建一個未初始化數組,我注意到方法1和3之間沒有明顯差異,而方法2需要更多的時間。這表明MATLAB在調用a = zeros(N,1)時避免顯式初始化數組爲0。

因此,我很好奇,想知道

  • 什麼是預分配在MATLAB的(未初始化)陣列的最佳方式? (最重要的是,大陣列)
  • 這是否也適用於八度?
+0

這很有趣。我認爲也許MATLAB不會初始化零,直到矩陣的修改完成(類似於matlab如何複製矩陣),而是「抽象的; a = NaN(1e4); a(1)= 1; toc'確實比'tic慢; a =零(1e4); a(1)= 1; toc'在我的機器上。就像剛纔那樣,我真的只看到用'零'完成的預分配,所以我很肯定沒有一種方法可以在沒有初始化的情況下進行預分配,除非你要製作一個mex例程,但也許這裏的其他人會知道。 – Justin

+2

這正在快速成爲一個Matlab常見問題,這個問題的各個方面已經在這裏介紹了。其他地方,比如在無價的Matlab博客 - http:// undocumentedmatlab上。com/blog/allocation-performance-take-2 /#more-4086隨着Matlab的發展,各種方法的相對速度似乎會改變。 –

+0

@Shai這是關於預分配方法的性能,而不是關於預分配的需要。請停止關閉這樣的問題。 – EJG89

回答

1

如何讓Matlab負責爲您分配分配?

clear a; 
for i=N:-1:1 
    a(i) = cos(i); 
end 

然後Matlab可以分配和填充數組,它認爲是最優的(可能爲零)。不過,您沒有NaNs的調試優勢。

+0

你有沒有計時?我會假設它與方法(3)相同 –

+2

我還沒有計時,但我也希望如此(對於大多數情況)。唯一可能的好處是你可以保證得到正確的類的變量 - 分配'a(n)= 0'可能不會,並且可能需要重新分配。 –

8

測試

利用MATLAB 2013b我和英特爾至強3.6GHz的+ 16GB RAM我跑到下面的代碼來分析。我區分了3種方法,只考慮了1D陣列,即矢量。已經使用列向量和行向量(即(n,1)和(1,n))對方法1和2進行了測試。

方法1(M1R,M1C)

a = zeros(1,n); 

方法2 M2R,M2C

a = NaN(1,n); 

方法3(M3)

a(n) = 0; 

結果

定時結果和元素數已繪製在圖時間間隔d的可變對數尺度。

timings1d

如圖所示的第三方法具有幾乎分配獨立矢量的大小,而另一穩定地增加暗示矢量的隱式定義。

討論

MatLab的不使用JIT(準時)大量的代碼優化,即優化代碼在運行時。因此,提出運行速度更快的代碼是否由於編程(無論優化是否相同)還是優化,都是一個有效的問題。要測試此優化可以通過使用功能('accel','off')關閉。運行代碼的結果再次是相當有趣:

timings1Dnoacceleration

結果表明,現在方法1是最優的,既爲行和列向量。方法3的行爲與第一個測試中的其他方法相似。

結論

優化內存預分配是無用的,MATLAB自浪費時間,無論如何都會優化爲您服務。

請注意,應該預先分配內存,但是您的操作方式並不重要。預分配內存的性能在很大程度上取決於MatLab的JIT編譯器是否選擇優化代碼。這完全依賴於.m-file的所有其他內容,因爲編譯器當時會考慮代碼塊,然後嘗試優化(它甚至有一個內存,因此多次運行文件可能會導致執行更低)時間)。考慮到性能與後來執行的計算相比,內存預分配通常是非常短的過程

在我看來,內存應該通過使用方法1或方法2來預先分配,以保持可讀代碼並使用函數MatLab幫助建議,因爲這些將來最有可能得到改進。

代碼使用

clear all 
clc 
feature('accel','on') 

number1D=30; 

nn1D=2.^(1:number1D); 

timings1D=zeros(5,number1D); 

for ii=1:length(nn1D); 
    n=nn1D(ii); 
    % 1D 
    tic 
    a = zeros(1,n); 
    a(randi(n,1))=1; 
    timings1D(1,ii)=toc; 
    fprintf('1D row vector method1 took: %f\n',timings1D(1,ii)) 
    clear a 

    tic 
    b = zeros(n,1); 
    b(randi(n,1))=1; 
    timings1D(2,ii)=toc; 
    fprintf('1D column vector method1 took: %f\n',timings1D(2,ii)) 
    clear b 

    tic 
    c = NaN(1,n); 
    c(randi(n,1))=1; 
    timings1D(3,ii)=toc; 
    fprintf('1D row vector method2 took: %f\n',timings1D(3,ii)) 
    clear c 

    tic 
    d = NaN(n,1); 
    d(randi(n,1))=1; 
    timings1D(4,ii)=toc; 
    fprintf('1D row vector method2 took: %f\n',timings1D(4,ii)) 
    clear d 

    tic 
    e(n) = 0; 
    e(randi(n,1))=1; 
    timings1D(5,ii)=toc; 
    fprintf('1D row vector method3 took: %f\n',timings1D(5,ii)) 
    clear e 
end 
logtimings1D = log10(timings1D); 
lognn1D=log10(nn1D); 
figure(1) 
clf() 
hold on 
plot(lognn1D,logtimings1D(1,:),'-k','LineWidth',2) 
plot(lognn1D,logtimings1D(2,:),'--k','LineWidth',2) 
plot(lognn1D,logtimings1D(3,:),'-.k','LineWidth',2) 
plot(lognn1D,logtimings1D(4,:),'-','Color',[0.6 0.6 0.6],'LineWidth',2) 
plot(lognn1D,logtimings1D(5,:),'--','Color',[0.6 0.6 0.6],'LineWidth',2) 
xlabel('Number of elements (log10[-])') 
ylabel('Timing of each method (log10[s])') 
legend('M1R','M1C','M2R','M2C','M3','Location','NW') 
title({'Various methods of pre-allocation in 1D','nr. of elements vs timing'}) 
hold off 

含有c(randi(n,1))=1的線;除了賦值給預分配數組中的一個隨機元素之外,不要做任何事情,以便數組用於挑戰JIT編譯器。這些線路不會顯着影響預分配測量,即它們不可測量,不會影響測試。

+0

不錯的測試,但'a(randi(n,1))= 1;'只初始化矢量的一個隨機元素嗎?聽起來對我來說很隨意。我寧願用'2 * i + 1'等初始化每個元素來防止一些可能的優化。 – Mathias

+0

該行旨在將1指定給隨機元素,以便JIT優化不會檢測到數組中沒有任何操作。它不會初始化任何東西,只會將一個分配給預先分配的零或nans數組。 – EJG89

+0

我添加了一個說明這些行的目的的說明。 – EJG89