arrayfun có thể chậm hơn đáng kể so với một vòng lặp rõ ràng trong matlab. Tại sao?


105

Hãy xem xét kiểm tra tốc độ đơn giản sau cho arrayfun:

T = 4000;
N = 500;
x = randn(T, N);
Func1 = @(a) (3*a^2 + 2*a - 1);

tic
Soln1 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln1(t, n) = Func1(x(t, n));
    end
end
toc

tic
Soln2 = arrayfun(Func1, x);
toc

Trên máy của tôi (Matlab 2011b trên Linux Mint 12), kết quả của bài kiểm tra này là:

Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.

Cái gì?!? arrayfun, mặc dù phải thừa nhận là một giải pháp trông sạch sẽ hơn, nhưng mức độ chậm hơn. Chuyện gì đang xảy ra ở đây?

Hơn nữa, tôi đã thực hiện một kiểu kiểm tra tương tự cho cellfunvà thấy nó chậm hơn khoảng 3 lần so với một vòng lặp rõ ràng. Một lần nữa, kết quả này trái ngược với những gì tôi mong đợi.

Câu hỏi của tôi là: Tại sao arrayfuncellfunchậm hơn rất nhiều? Và với điều này, có bất kỳ lý do chính đáng nào để sử dụng chúng (ngoài việc làm cho mã trông đẹp) không?

Lưu ý: Tôi đang nói về phiên bản tiêu chuẩn arrayfunở đây, KHÔNG phải phiên bản GPU từ hộp công cụ xử lý song song.

EDIT: Chỉ cần nói rõ, tôi biết rằng phần Func1trên có thể được vector hóa như đã chỉ ra bởi Oli. Tôi chỉ chọn nó vì nó mang lại một bài kiểm tra tốc độ đơn giản cho các mục đích của câu hỏi thực tế.

CHỈNH SỬA: Theo gợi ý của grungetta, tôi đã làm lại bài kiểm tra với feature accel off. Kết quả là:

Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.

Nói cách khác, có vẻ như một phần lớn của sự khác biệt là trình tăng tốc JIT thực hiện công việc tăng tốc forvòng lặp rõ ràng tốt hơn nhiều so với nó arrayfun. Điều này có vẻ kỳ lạ đối với tôi, vì arrayfunthực sự cung cấp nhiều thông tin hơn, tức là, việc sử dụng nó tiết lộ rằng thứ tự của các lệnh gọi Func1không quan trọng. Ngoài ra, tôi lưu ý rằng cho dù bộ tăng tốc JIT được bật hay tắt, hệ thống của tôi chỉ sử dụng một CPU ...


10
May mắn thay, "giải pháp tiêu chuẩn" vẫn là nhanh nhất cho đến nay: tic; 3 * x. ^ 2 + 2 * x-1; toc Thời gian đã trôi qua là 0,030662 giây.
Oli

4
@Oli Tôi cho rằng tôi nên đã dự đoán rằng ai đó sẽ chỉ ra điều này và sử dụng một chức năng mà không thể được vector :-)
Colin T Bowers

3
Tôi muốn xem thời gian này thay đổi như thế nào khi trình tăng tốc JIT bị tắt. Thực hiện lệnh 'tính năng tích lũy' và sau đó chạy lại thử nghiệm của bạn.
grungetta

@grungetta Gợi ý thú vị. Tôi đã thêm kết quả vào câu hỏi cùng với một vài nhận xét.
Colin T Bowers

Câu trả lời:


101

Bạn có thể lấy ý tưởng bằng cách chạy các phiên bản mã khác của mình. Cân nhắc viết ra các phép tính một cách rõ ràng, thay vì sử dụng một hàm trong vòng lặp của bạn

tic
Soln3 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln3(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
    end
end
toc

Đã đến lúc tính toán trên máy tính của tôi:

Soln1  1.158446 seconds.
Soln2  10.392475 seconds.
Soln3  0.239023 seconds.
Oli    0.010672 seconds.

Bây giờ, trong khi giải pháp 'vectơ hóa đầy đủ' rõ ràng là nhanh nhất, bạn có thể thấy rằng việc xác định một hàm được gọi cho mọi mục nhập x là một chi phí lớn . Chỉ cần viết ra tính toán một cách rõ ràng đã giúp chúng tôi tăng tốc hệ số 5. Tôi đoán điều này cho thấy rằng trình biên dịch MATLABs JIT không hỗ trợ các hàm nội tuyến . Theo câu trả lời của gnovice ở đó, thực sự tốt hơn nên viết một hàm bình thường hơn là một hàm ẩn danh. Thử nó.

Bước tiếp theo - loại bỏ (vectơ hóa) vòng lặp bên trong:

tic
Soln4 = ones(T, N);
for t = 1:T
    Soln4(t, :) = 3*x(t, :).^2 + 2*x(t, :) - 1;
end
toc

Soln4  0.053926 seconds.

Một yếu tố thứ 5 khác là tăng tốc: có điều gì đó trong những câu nói đó nói rằng bạn nên tránh các vòng lặp trong MATLAB ... Hay là có thực sự? Hãy xem cái này sau đó

tic
Soln5 = ones(T, N);
for n = 1:N
    Soln5(:, n) = 3*x(:, n).^2 + 2*x(:, n) - 1;
end
toc

Soln5   0.013875 seconds.

Gần hơn nhiều so với phiên bản vector hóa 'đầy đủ'. Matlab lưu trữ ma trận theo cột. Bạn nên luôn (khi có thể) cấu trúc các tính toán của mình thành vectơ hóa 'cột khôn ngoan'.

Chúng ta có thể quay lại Soln3 ngay bây giờ. Thứ tự vòng lặp ở đó là 'hàng khôn ngoan'. Hãy thay đổi nó

tic
Soln6 = ones(T, N);
for n = 1:N
    for t = 1:T
        Soln6(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
    end
end
toc

Soln6  0.201661 seconds.

Tốt hơn, nhưng vẫn rất tệ. Vòng lặp đơn - tốt. Vòng lặp kép - xấu. Tôi đoán MATLAB đã làm một số công việc tốt trong việc cải thiện hiệu suất của các vòng lặp, nhưng chi phí vòng lặp vẫn ở đó. Nếu bạn có một số công việc nặng hơn bên trong, bạn sẽ không nhận thấy. Nhưng vì tính toán này bị giới hạn băng thông bộ nhớ, bạn sẽ thấy chi phí vòng lặp. Và bạn thậm chí sẽ thấy rõ hơn chi phí của việc gọi Func1 ở đó.

Vậy có vấn đề gì với arrayfun? Không có chức năng nào ở đó, vì vậy rất nhiều chi phí. Nhưng tại sao lại tệ hơn nhiều so với vòng lặp lồng nhau? Trên thực tế, chủ đề sử dụng cellfun / arrayfun đã được thảo luận rộng rãi nhiều lần (ví dụ: đây , đây , đâyđây ). Các chức năng này chỉ đơn giản là chậm, bạn không thể sử dụng chúng cho các tính toán chi tiết như vậy. Bạn có thể sử dụng chúng để viết mã ngắn gọn và chuyển đổi lạ mắt giữa các ô và mảng. Nhưng hàm cần nặng hơn những gì bạn đã viết:

tic
Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false);
toc

Soln7  0.016786 seconds.

Lưu ý rằng Soln7 là một tế bào bây giờ .. đôi khi điều đó hữu ích. Hiệu suất mã hiện khá tốt và nếu bạn cần ô làm đầu ra, bạn không cần phải chuyển đổi ma trận của mình sau khi bạn đã sử dụng giải pháp vector hóa hoàn toàn.

Vậy tại sao arrayfun lại chậm hơn cấu trúc vòng lặp đơn giản? Thật không may, chúng tôi không thể nói chắc chắn, vì không có mã nguồn nào. Bạn chỉ có thể đoán rằng vì arrayfun là một hàm mục đích chung, xử lý tất cả các loại cấu trúc dữ liệu và đối số khác nhau, nó không nhất thiết phải rất nhanh trong các trường hợp đơn giản, mà bạn có thể trực tiếp biểu thị dưới dạng tổ vòng lặp. Chúng tôi không thể biết chi phí đến từ đâu. Có thể tránh được chi phí bằng cách triển khai tốt hơn không? Có thể không. Nhưng không may, điều duy nhất chúng ta có thể làm là nghiên cứu hiệu suất để xác định các trường hợp, trường hợp nào hoạt động tốt và trường hợp nào không.

Cập nhật Vì thời gian thực hiện bài kiểm tra này ngắn, để có được kết quả đáng tin cậy, tôi đã thêm một vòng lặp xung quanh các bài kiểm tra:

for i=1:1000
   % compute
end

Một số lần đưa ra dưới đây:

Soln5   8.192912 seconds.
Soln7  13.419675 seconds.
Oli     8.089113 seconds.

Bạn thấy rằng arrayfun vẫn tệ, nhưng ít nhất không kém hơn ba bậc độ lớn so với giải pháp vector hóa. Mặt khác, một vòng lặp đơn với các phép tính theo cột cũng nhanh như phiên bản được vector hóa hoàn toàn ... Tất cả đều được thực hiện trên một CPU duy nhất. Kết quả cho Soln5 và Soln7 không thay đổi nếu tôi chuyển sang 2 lõi - Trong Soln5, tôi sẽ phải sử dụng một parfor để ghép nó song song. Quên về tăng tốc độ ... Soln7 không chạy song song vì arrayfun không chạy song song. Mặt khác, phiên bản vector hóa của Olis:

Oli  5.508085 seconds.

9
Câu trả lời chính xác! Và các liên kết đến trung tâm matlab đều cung cấp các bài đọc rất thú vị. Cảm ơn nhiều.
Colin T Bowers

Đây là một phân tích tốt đẹp.
H.Muster

Và một bản cập nhật thú vị! Câu trả lời này cứ tiếp tục đưa ra :-)
Colin T Bowers

3
chỉ là một nhận xét nhỏ; trở lại MATLAB 6.5, cellfunđược triển khai dưới dạng tệp MEX (với mã nguồn C có sẵn bên cạnh). Nó thực sự khá đơn giản. Tất nhiên nó chỉ hỗ trợ việc áp dụng một trong 6 chức năng mã hóa cứng (bạn couldnt vượt qua một chức năng xử lý, chỉ có một chuỗi với một trong những tên hàm)
Amro

1
arrayfun + function xử lý = chậm! tránh chúng trong mã nặng.
Yvon

-8

Đó bởi vì!!!!

x = randn(T, N); 

không phải là gpuarrayloại;

Tất cả những gì bạn cần làm là

x = randn(T, N,'gpuArray');

2
Tôi nghĩ bạn cần đọc kỹ hơn một chút câu hỏi và câu trả lời xuất sắc của @angainor. Nó không có bất cứ điều gì để làm với gpuarray. Đó gần như chắc chắn là lý do tại sao câu trả lời này đã bị từ chối.
Colin T Bowers

@Colin - Tôi đồng ý rằng câu trả lời của angainor là kỹ lưỡng hơn, nhưng câu trả lời không đề cập đến 'gpuArray'. Tôi nghĩ rằng 'gpuArray' là một đóng góp tốt ở đây (nếu nó đúng). Ngoài ra, câu hỏi hơi cẩu thả với "Chuyện gì đang xảy ra ở đây?" , vì vậy tôi nghĩ rằng nó đã mở ra cánh cửa cho các phương pháp bổ sung như vectơ hóa dữ liệu và chuyển nó sang GPU. Tôi để câu trả lời này đi vì nó có thể tăng giá trị cho những khách truy cập trong tương lai. Tôi xin lỗi nếu tôi gọi nhầm.
jww

1
Bạn cũng quên thực tế gpuarraylà chỉ hỗ trợ cho cạc đồ họa nVidia. Nếu họ không có phần cứng như vậy, thì lời khuyên của bạn (hoặc thiếu) là vô nghĩa. -1
rayryeng

Mặt khác, gpuarray là công cụ nhẹ nhàng của lập trình vectơ matlab.
MrIO
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.