2013-08-16 65 views
11

Một trong những điều đầu tiên người ta học về lập trình hiệu quả trong MATLAB là tránh các mảng thay đổi kích thước động. Ví dụ tiêu chuẩn như sau.Preallocation hiệu quả mảng trong 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 

Các 'Bad' biến thể ở đây đòi hỏi O (N^2) thời gian để chạy, vì nó phải phân bổ một mảng mới và sao chép các giá trị cũ tại mỗi lần lặp của vòng lặp.

Thực hành ưa thích của riêng tôi khi gỡ lỗi là phân bổ một mảng với NaN, khó gây nhầm lẫn hơn với giá trị hợp lệ hơn 0.

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

Tuy nhiên, một ngây thơ nghĩ rằng một khi mã của chúng tôi là sửa lỗi, chúng tôi đang lãng phí thời gian bằng cách phân bổ một mảng và sau đó điền nó với 0 hoặc NaN. Như đã đề cập here, bạn có lẽ có thể tạo một mảng chưa được khởi tạo như sau

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

Tuy nhiên, trong các thử nghiệm của riêng tôi (MATLAB R2013a), tôi nhận thấy có sự khác biệt đáng kể giữa các phương pháp 1 và 3, trong khi phương pháp 2 mất nhiều thời gian hơn. Điều này cho thấy rằng MATLAB đã tránh việc khởi tạo một cách rõ ràng mảng thành 0 khi gọi a = zeros(N,1).

Vì vậy, tôi tò mò muốn biết

  • cách tối ưu để preallocate một mảng (chưa được khởi tạo) trong MATLAB là gì? (Quan trọng nhất, mảng lớn)
  • Điều này cũng có giữ trong Octave không?
+0

Điều này thật thú vị. Tôi nghĩ có lẽ MATLAB đã không khởi tạo các số không cho đến khi một sửa đổi của ma trận đã được thực hiện (tương tự như cách ma trận bản sao MATLAB), nhưng 'tic; a = NaN (1e4); a (1) = 1; toc' thực sự chậm hơn 'tic; a = số không (1e4); a (1) = 1; toc' trên máy tính của tôi. Tuy nhiên, tôi thực sự chỉ thấy preallocation được thực hiện với 'số không' vì vậy tôi khá chắc chắn không có cách để preallocate mà không khởi tạo trừ khi bạn đã thực hiện một thói quen mex, nhưng có lẽ những người khác ở đây sẽ biết . – Justin

+2

Điều này nhanh chóng trở thành Câu hỏi thường gặp về Matlab và các khía cạnh của câu hỏi đã được đề cập ở đây trên SO. Ở những nơi khác quá, chẳng hạn như trên blog Matlab không có giấy tờ vô giá - http: // undocumentedmatlab.com/blog/alloc-performance-take-2/# more-4086 Tốc độ so sánh của các cách tiếp cận khác nhau dường như thay đổi khi Matlab phát triển. –

+0

@Shai đây là về hiệu suất của các phương pháp phân bổ trước, không phải về nhu cầu phân bổ trước. Ngừng đóng câu hỏi như thế này. – EJG89

Trả lời

1

Làm cách nào để Matlab quản lý việc phân bổ cho bạn?

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

Sau đó, Matlab có thể phân bổ và lấp đầy mảng bằng bất kỳ thứ gì mà nó cho là tối ưu (có thể là không). Tuy nhiên, bạn không có lợi thế gỡ lỗi là NaNs.

+0

Bạn đã hẹn giờ này chưa? Tôi sẽ giả sử nó giống như phương pháp (3) –

+2

Tôi đã không hẹn giờ nó, nhưng tôi cũng mong đợi như vậy (đối với hầu hết các trường hợp). Lợi ích duy nhất có thể là bạn được đảm bảo để có được một biến của lớp đúng - gán 'a (n) = 0' có thể không, và sau đó có thể yêu cầu phân bổ lại. –

8

Bài kiểm tra

Sử dụng Matlab 2013b I và một 3.6GHz Intel Xeon + 16GB RAM Tôi chạy đoạn code dưới đây để cấu hình. Tôi phân biệt 3 phương pháp và chỉ xem xét mảng 1D, tức là vectơ. Phương pháp 1 và 2 đã được thử nghiệm bằng cách sử dụng cả vectơ cột và vectơ hàng, tức là (n, 1) và (1, n).

Phương pháp 1 (M1R, M1C)

a = zeros(1,n); 

Phương pháp 2 M2R, M2C

a = NaN(1,n); 

Phương pháp 3 (M3)

a(n) = 0; 

Kết quả

Kết quả tính thời gian và số lượng phần tử đã được vẽ trên thang đo logarit trong hình thời gian1D.

timings1d

Như đã trình bày phương pháp thứ ba có nhiệm vụ gần như không phụ thuộc vào kích thước vector trong khi người kia đều đặn tăng cho thấy một định nghĩa tiềm ẩn của vector.

Thảo luận

MatLab hiện rất nhiều tối ưu hóa mã sử dụng JIT (Just in time), ví dụ: mã tối ưu hóa trong thời gian chạy. Vì vậy, nó là một câu hỏi hợp lệ để đặt ra có hay không một phần của mã chạy nhanh hơn là do lập trình (luôn luôn giống nhau hay không được tối ưu hóa) hoặc do tối ưu hóa. Để kiểm tra tối ưu hóa này có thể được tắt bằng cách sử dụng tính năng ('tăng tốc', 'tắt'). Kết quả của chạy mã lại khá thú vị:

timings1Dnoacceleration

Nó được hiển thị mà bây giờ Phương pháp 1 là tối ưu, cho cả hàng và cột vectơ. Và phương pháp 3 hoạt động giống như các phương pháp khác trong bài kiểm tra đầu tiên.

Kết luận

Tối ưu hóa bộ nhớ trước khi phân bổ là vô ích và lãng phí thời gian kể từ Matlab sẽ tối ưu hóa cho bạn anyway.

Lưu ý rằng bộ nhớ nên được phân bổ trước nhưng cách bạn làm việc đó không quan trọng. Hiệu suất của bộ nhớ phân bổ trước phần lớn phụ thuộc vào việc trình biên dịch JIT của MatLab có chọn tối ưu hóa mã của bạn hay không. Điều này hoàn toàn phụ thuộc vào tất cả nội dung khác của tệp .m của bạn vì trình biên dịch xem xét các đoạn mã tại thời điểm đó và sau đó cố gắng tối ưu hóa (nó thậm chí có bộ nhớ để chạy tệp nhiều lần có thể dẫn đến việc thực thi thậm chí thấp hơn thời gian). Việc phân bổ trước bộ nhớ thường là một quá trình rất ngắn để xem xét hiệu năng so với tính toán được thực hiện sau đó

Trong bộ nhớ ý kiến ​​của tôi nên được phân bổ trước bằng cách sử dụng phương pháp 1 hoặc phương pháp 2 để duy trì mã có thể đọc được và sử dụng hàm mà MatLab giúp gợi ý vì đây là những khả năng được cải thiện nhất trong tương lai.

Mã sử ​​dụng

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 

Note

Các dòng chứa c(randi(n,1))=1; không làm bất cứ điều gì ngoại trừ việc gán giá trị một cho một phần tử ngẫu nhiên trong mảng được phân bổ trước để mảng được sử dụng để thử thách trình biên dịch JIT một chút. Những dòng này không ảnh hưởng đáng kể đến đo lường phân bổ trước đáng kể, nghĩa là chúng không thể đo lường được và không ảnh hưởng đến thử nghiệm.

+0

Thử nghiệm tốt đẹp, nhưng 'a (randi (n, 1)) = 1;' chỉ khởi tạo một phần tử ngẫu nhiên của vectơ không? Âm thanh khá tùy tiện với tôi. Tôi muốn khởi tạo mỗi phần tử với một cái gì đó như '2 * i + 1' để ngăn chặn một số tối ưu có thể. – Mathias

+0

Dòng đó có nghĩa là gán 1 cho một phần tử ngẫu nhiên để tối ưu hóa JIT không phát hiện ra rằng không có gì được thực hiện với các mảng. Nó không khởi tạo bất cứ điều gì, nhưng chỉ gán một cho một mảng hoặc số không được phân bổ trước. – EJG89

+0

Tôi đã thêm một ghi chú giải thích mục đích của những dòng này. – EJG89

Các vấn đề liên quan