arrayfun bisa jauh lebih lambat daripada loop eksplisit di matlab. Mengapa?


105

Pertimbangkan tes kecepatan sederhana berikut untuk 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

Di komputer saya (Matlab 2011b di Linux Mint 12), output dari pengujian ini adalah:

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

Apa itu?!? arrayfun, meskipun memang solusi yang tampak lebih bersih, urutan besarnya lebih lambat. Apa yang terjadi disini?

Selanjutnya, saya melakukan gaya pengujian yang serupa cellfundan menemukan itu menjadi sekitar 3 kali lebih lambat dari loop eksplisit. Sekali lagi, hasil ini berlawanan dengan yang saya harapkan.

Pertanyaan saya adalah: Mengapa arrayfundan cellfunjauh lebih lambat? Dan mengingat ini, apakah ada alasan bagus untuk menggunakannya (selain untuk membuat kode terlihat bagus)?

Catatan: Saya berbicara tentang versi standar di arrayfunsini, BUKAN versi GPU dari kotak alat pemrosesan paralel.

EDIT: Untuk memperjelas, saya sadar bahwa di Func1atas dapat vektorisasi seperti yang ditunjukkan oleh Oli. Saya hanya memilihnya karena menghasilkan tes kecepatan sederhana untuk tujuan pertanyaan sebenarnya.

EDIT: Mengikuti saran grungetta, saya melakukan tes ulang dengan feature accel off. Hasilnya adalah:

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

Dengan kata lain, akan terlihat bahwa sebagian besar perbedaannya adalah akselerator JIT melakukan pekerjaan yang jauh lebih baik untuk mempercepat forloop eksplisit daripada yang dilakukannya arrayfun. Ini tampak aneh bagi saya, karena arrayfunsebenarnya memberikan lebih banyak informasi, yaitu, penggunaannya mengungkapkan bahwa urutan panggilan ke Func1tidak penting. Juga, saya mencatat bahwa apakah akselerator JIT dihidupkan atau dimatikan, sistem saya hanya pernah menggunakan satu CPU ...


10
Untungnya, sejauh ini "solusi standar" tetap yang tercepat: tic; 3 * x. ^ 2 + 2 * x-1; toc Waktu yang berlalu adalah 0,030662 detik.
Oli

4
@Oli Saya kira saya seharusnya mengantisipasi bahwa seseorang akan menunjukkan hal ini dan menggunakan fungsi yang tidak dapat di-vektorisasi :-)
Colin T Bowers

3
Saya tertarik untuk melihat bagaimana pengaturan waktu ini berubah saat akselerator JIT dimatikan. Jalankan perintah 'fitur akselerasi' dan kemudian jalankan kembali pengujian Anda.
grungetta

@grungetta Saran menarik. Saya telah menambahkan hasil ke pertanyaan bersama dengan beberapa komentar.
Colin T Bowers

izinkan saya menambahkan yang ini ke daftar pertanyaan terkait: Apa cara tercepat untuk melakukan operasi aritmatika pada setiap elemen array sel?
Amro

Jawaban:


101

Anda bisa mendapatkan ide dengan menjalankan versi lain dari kode Anda. Pertimbangkan untuk menulis perhitungan secara eksplisit, daripada menggunakan fungsi dalam loop Anda

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

Waktu untuk menghitung di komputer saya:

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

Sekarang, meskipun solusi 'vektorisasi' sepenuhnya jelas merupakan yang tercepat, Anda dapat melihat bahwa menentukan fungsi yang akan dipanggil untuk setiap entri x adalah biaya tambahan yang sangat besar . Hanya secara eksplisit menulis perhitungan memberi kami faktor kecepatan 5. Saya rasa ini menunjukkan bahwa MATLABs JIT compiler tidak mendukung fungsi inline . Menurut jawaban gnovice di sana, sebenarnya lebih baik menulis fungsi normal daripada yang anonim. Cobalah.

Langkah selanjutnya - hapus (vektorisasi) loop dalam:

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.

Faktor lain 5 percepatan: ada sesuatu dalam pernyataan itu yang mengatakan Anda harus menghindari loop di MATLAB ... Atau benarkah? Lihat ini kalau begitu

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.

Lebih dekat dengan versi vektorisasi 'sepenuhnya'. Matlab menyimpan matriks berdasarkan kolom. Anda harus selalu (jika mungkin) menyusun komputasi Anda menjadi 'kolom-bijaksana' vektor.

Kita bisa kembali ke Soln3 sekarang. Urutan loop ada 'baris-bijaksana'. Mari kita ubah

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.

Lebih baik, tapi tetap sangat buruk. Putaran tunggal - bagus. Putaran ganda - buruk. Saya kira MATLAB melakukan beberapa pekerjaan yang layak untuk meningkatkan kinerja loop, tetapi overhead loop masih ada. Jika Anda memiliki pekerjaan yang lebih berat di dalam, Anda tidak akan menyadarinya. Tetapi karena komputasi ini dibatasi bandwidth memori, Anda akan melihat overhead loop. Dan Anda bahkan akan lebih jelas melihat overhead pemanggilan Func1 di sana.

Jadi ada apa dengan arrayfun? Tidak ada fungsi inlinig di sana, jadi banyak overhead. Tetapi mengapa jauh lebih buruk daripada loop bersarang ganda? Sebenarnya topik penggunaan cellfun / arrayfun sudah banyak dibahas berkali-kali (misalnya disini , disini , disini dan disini ). Fungsi-fungsi ini lambat sekali, Anda tidak dapat menggunakannya untuk perhitungan halus seperti itu. Anda dapat menggunakannya untuk singkatnya kode dan konversi mewah antara sel dan array. Tetapi fungsinya harus lebih berat dari yang Anda tulis:

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

Soln7  0.016786 seconds.

Perhatikan bahwa Soln7 sekarang adalah sel .. terkadang itu berguna. Kinerja kode cukup baik sekarang, dan jika Anda membutuhkan sel sebagai keluaran, Anda tidak perlu mengonversi matriks Anda setelah Anda menggunakan solusi vektor penuh.

Jadi mengapa arrayfun lebih lambat dari struktur loop sederhana? Sayangnya, kami tidak dapat mengatakan dengan pasti, karena tidak ada kode sumber yang tersedia. Anda hanya dapat menebaknya karena arrayfun adalah fungsi bertujuan umum, yang menangani semua jenis struktur data dan argumen yang berbeda, tidak harus sangat cepat dalam kasus sederhana, yang dapat Anda ekspresikan secara langsung sebagai sarang loop. Dari mana datangnya biaya overhead, kita tidak bisa tahu. Bisakah overhead dihindari dengan implementasi yang lebih baik? Mungkin tidak. Tapi sayangnya satu-satunya hal yang dapat kita lakukan adalah mempelajari kinerja untuk mengidentifikasi kasus-kasus, di mana itu bekerja dengan baik, dan di mana tidak.

Perbarui Karena waktu pelaksanaan tes ini singkat, untuk mendapatkan hasil yang andal, saya menambahkan sekarang putaran di sekitar tes:

for i=1:1000
   % compute
end

Beberapa waktu yang diberikan di bawah ini:

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

Anda melihat bahwa arrayfun masih buruk, tetapi setidaknya tidak tiga kali lipat lebih buruk daripada solusi vektor. Di sisi lain, satu loop dengan perhitungan berdasarkan kolom secepat versi vektor penuh ... Itu semua dilakukan pada satu CPU. Hasil untuk Soln5 dan Soln7 tidak berubah jika saya beralih ke 2 core - Dalam Soln5 saya harus menggunakan parfor untuk membuatnya diparalelkan. Lupakan tentang speedup ... Soln7 tidak berjalan secara paralel karena arrayfun tidak berjalan secara paralel. Versi vektor Olis di sisi lain:

Oli  5.508085 seconds.

9
Jawaban yang bagus! Dan tautan ke pusat matlab semuanya memberikan bacaan yang sangat menarik. Terimakasih banyak.
Colin T Bowers

Ini adalah analisis yang bagus.
H. Muster

Dan pembaruan yang menarik! Jawaban ini terus memberi :-)
Colin T Bowers

3
hanya komentar kecil; kembali ke MATLAB 6.5, cellfundiimplementasikan sebagai file MEX (dengan kode sumber C tersedia di sampingnya). Sebenarnya cukup mudah. Tentu saja itu hanya mendukung penerapan salah satu dari 6 fungsi hard-coded (Anda tidak bisa melewatkan sebuah pegangan fungsi, hanya sebuah string dengan satu nama fungsi)
Amro

1
arrayfun + function handle = lambat! hindari mereka dalam kode yang berat.
Yvon

-8

Karena itu!!!!

x = randn(T, N); 

bukan gpuarraytipe;

Yang perlu Anda lakukan hanyalah

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

2
Saya pikir Anda perlu membaca pertanyaan dan jawaban yang sangat baik oleh @angainor sedikit lebih hati-hati. Itu tidak ada hubungannya dengan gpuarray. Hampir pasti itulah mengapa jawaban ini tidak disukai.
Colin T Bowers

@ Colin - Saya setuju angainor lebih menyeluruh, tetapi jawabannya tidak menyebutkan 'gpuArray'. Saya pikir 'gpuArray' adalah kontribusi yang baik di sini (jika benar). Juga, pertanyaannya menjadi sedikit ceroboh dengan "Apa yang terjadi di sini?" , jadi saya pikir itu membuka pintu untuk metode tambahan seperti data vektorisasi dan mengirimkannya ke GPU. Saya membiarkan jawaban ini naik karena bisa menambah nilai bagi pengunjung di masa depan. Mohon maaf jika saya melakukan panggilan yang salah.
jww

1
Anda juga melupakan fakta yang gpuarrayhanya didukung untuk kartu grafis nVidia. Jika mereka tidak memiliki perangkat keras seperti itu, maka saran Anda (atau kekurangan) tidak ada artinya. -1
rayryeng

Di sisi lain, gpuarray adalah pedang cahaya dari pemrograman vektor matlab.
MrIO
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.