Apakah MATLAB OOP lambat atau apakah saya melakukan sesuatu yang salah?

144

Aku bereksperimen dengan MATLAB OOP , sebagai mulai saya menirukan saya C ++ 's kelas Logger dan aku menempatkan semua saya fungsi pembantu string dalam kelas String, berpikir itu akan menjadi besar untuk dapat melakukan hal-hal seperti a + b, a == b, a.find( b )bukannya strcat( a b ), strcmp( a, b ), ambil elemen pertama strfind( a, b ), dll.

Masalahnya: perlambatan

Saya menggunakan hal-hal di atas untuk digunakan dan segera melihat penurunan drastis . Apakah saya melakukan kesalahan (yang tentu saja mungkin karena saya memiliki pengalaman MATLAB yang agak terbatas), atau apakah OOP MATLAB hanya memperkenalkan banyak overhead?

Kasing uji saya

Inilah tes sederhana yang saya lakukan untuk string, pada dasarnya hanya menambahkan string dan menghapus bagian yang ditambahkan lagi:

Catatan: Jangan benar-benar menulis kelas String seperti ini dalam kode nyata! Matlab memiliki stringtipe array asli sekarang, dan Anda harus menggunakannya.

classdef String < handle
  ....
  properties
    stringobj = '';
  end
  function o = plus( o, b )
    o.stringobj = [ o.stringobj b ];
  end
  function n = Length( o )
    n = length( o.stringobj );
  end
  function o = SetLength( o, n )
    o.stringobj = o.stringobj( 1 : n );
  end
end

function atest( a, b ) %plain functions
  n = length( a );
  a = [ a b ];
  a = a( 1 : n );

function btest( a, b ) %OOP
  n = a.Length();
  a = a + b;
  a.SetLength( n );

function RunProfilerLoop( nLoop, fun, varargin )
  profile on;
  for i = 1 : nLoop
    fun( varargin{ : } );
  end
  profile off;
  profile report;

a = 'test';
aString = String( 'test' );
RunProfilerLoop( 1000, @(x,y)atest(x,y), a, 'appendme' );
RunProfilerLoop( 1000, @(x,y)btest(x,y), aString, 'appendme' );

Hasil

Total waktu dalam detik, untuk 1000 iterasi:

btest 0,550 (dengan String.SetLength 0,138, String.plus 0,065, String.Length 0,057)

atest 0,015

Hasil untuk sistem logger juga: 0,1 detik untuk 1000 panggilan ke frpintf( 1, 'test\n' ), 7 (!) Detik untuk 1000 panggilan ke sistem saya ketika menggunakan kelas String secara internal (OK, ia memiliki lebih banyak logika di dalamnya, tetapi dibandingkan dengan C ++: overhead sistem saya yang menggunakan std::string( "blah" )dan std::coutpada sisi output vs polos std::cout << "blah"berada di urutan 1 milidetik.)

Apakah itu hanya overhead saat mencari fungsi kelas / paket?

Karena MATLAB diinterpretasikan, ia harus mencari definisi fungsi / objek pada saat run time. Jadi saya bertanya-tanya bahwa mungkin lebih banyak overhead yang terlibat dalam mencari kelas atau fungsi paket vs fungsi yang ada di jalur. Saya mencoba untuk menguji ini, dan itu hanya menjadi asing. Untuk mengesampingkan pengaruh kelas / objek, saya membandingkan memanggil fungsi di path vs fungsi dalam sebuah paket:

function n = atest( x, y )
  n = ctest( x, y ); % ctest is in matlab path

function n = btest( x, y )
  n = util.ctest( x, y ); % ctest is in +util directory, parent directory is in path

Hasil, dikumpulkan dengan cara yang sama seperti di atas:

atest 0,004 dtk, 0,001 dtk dalam ctest

btest 0,060 dtk, 0,014 dtk di util.ctest

Jadi, apakah semua overhead ini hanya berasal dari MATLAB yang menghabiskan waktu mencari definisi untuk implementasi OOPnya, sedangkan overhead ini tidak ada untuk fungsi-fungsi yang secara langsung berada di jalurnya?

stijn
sumber
5
Terima kasih atas pertanyaan ini! Kinerja tumpukan Matlab (OOP / closures) telah mengganggu saya selama bertahun-tahun, lihat stackoverflow.com/questions/1446281/matlabs-garbage-collector . Saya benar-benar ingin tahu apa yang MatlabDoug / Loren / MikeKatz akan menanggapi posting Anda.
Mikhail
1
^ itu bacaan yang menarik.
stijn
1
@ MatlabDoug: mungkin kolega Anda, Mike Karr, bisa berkomentar OP?
Mikhail
4
Pembaca juga harus memeriksa posting blog terakhir ini (oleh Dave Foti) yang membahas kinerja OOP dalam versi R2012a terbaru: Mempertimbangkan Kinerja dalam Kode MATLAB yang Berorientasi Objek
Amro
1
Contoh sederhana sensitivitas pada struktur kode di mana pemanggilan metode subelemen diambil dari loop. for i = 1:this.get_n_quantities() if(strcmp(id,this.get_quantity_rlz(i).get_id())) ix = i; end endmemakan waktu 2,2 detik, sementara nq = this.get_n_quantities(); a = this.get_quantity_realizations(); for i = 1:nq c = a{i}; if(strcmp(id,c.get_id())) ix = i; end endmemakan waktu 0,01, dua pesanan mag
Jose Ospina

Jawaban:

223

Saya telah bekerja dengan OO MATLAB untuk sementara waktu, dan akhirnya melihat masalah kinerja yang serupa.

Jawaban singkatnya adalah: ya, OOP MATLAB agak lambat. Ada overhead panggilan metode yang substansial, lebih tinggi dari bahasa OO arus utama, dan tidak banyak yang dapat Anda lakukan. Sebagian alasannya mungkin karena MATLAB idiomatik menggunakan kode "vektor" untuk mengurangi jumlah panggilan metode, dan overhead per panggilan bukan prioritas tinggi.

Saya membandingkan kinerja dengan menulis fungsi "nop" do-nothing sebagai berbagai jenis fungsi dan metode. Berikut adalah beberapa hasil yang khas.

>> call_nops
Komputer: PCWIN Rilis: 2009b
Memanggil setiap fungsi / metode 100000 kali
fungsi nop (): 0,02261 detik 0,23 usec per panggilan
nop1-5 () fungsi: 0,02182 detik 0,22 usec per panggilan
subfungsi nop (): 0,02244 dt 0,22 usec per panggilan
@ () [] fungsi anonim: 0,08461 detik 0,85 usec per panggilan
nop (obj) method: 0,24664 dtk 2,47 usec per panggilan
metode nop1-5 (obj): 0,23469 dtk 2,35 usec per panggilan
nop () fungsi pribadi: 0,02197 detik 0,22 usec per panggilan
classdef nop (obj): 0.90547 dt 9.05 usec per panggilan
classdef obj.nop (): 1,75522 detik 17,55 usec per panggilan
classdef private_nop (obj): 0.84738 detik 8.47 usec per panggilan
classdef nop (obj) (m-file): 0,90560 dt 9,06 usec per panggilan
classdef class.staticnop (): 1,16361 detik 11,64 usec per panggilan
Java nop (): 2,43035 detik 24,30 usec per panggilan
Java static_nop (): 0,87682 dtk 8,77 usec per panggilan
Java nop () dari Java: 0,00014 detik 0,00 usec per panggilan
MEX mexnop (): 0,11409 dtk 1,14 usec per panggilan
C nop (): 0,00001 dtk 0,00 usec per panggilan

Hasil serupa pada R2008a hingga R2009b. Ini pada Windows XP x64 yang menjalankan MATLAB 32-bit.

"Java nop ()" adalah metode Java yang tidak melakukan apa pun yang dipanggil dari dalam loop kode-M, dan termasuk overhead pengiriman MATLAB-ke-Java dengan setiap panggilan. "Java nop () from Java" adalah hal yang sama yang disebut dalam Java for () loop dan tidak dikenakan penalti batas itu. Ambil pewaktuan Java dan C dengan sebutir garam; kompiler yang cerdik dapat mengoptimalkan panggilan secara total.

Mekanisme pelingkupan paket baru, diperkenalkan pada waktu yang hampir bersamaan dengan kelas classdef. Perilakunya mungkin terkait.

Beberapa kesimpulan sementara:

  • Metode lebih lambat dari fungsi.
  • Metode gaya baru (classdef) lebih lambat dari metode gaya lama.
  • obj.nop()Sintaks baru lebih lambat dari nop(obj)sintaks, bahkan untuk metode yang sama pada objek classdef. Sama untuk objek Java (tidak ditampilkan). Jika Anda ingin cepat, hubungi nop(obj).
  • Metode panggilan overhead lebih tinggi (sekitar 2x) di MATLAB 64-bit pada Windows. (Tidak ditampilkan.)
  • Pengiriman metode MATLAB lebih lambat dari beberapa bahasa lain.

Mengatakan mengapa ini hanya akan menjadi spekulasi di pihak saya. OO internal engine MATLAB tidak bersifat publik. Ini bukan masalah yang ditafsirkan vs dikompilasi per se - MATLAB memiliki JIT - tetapi mengetik dan sintaksis MATLAB yang lebih longgar mungkin berarti lebih banyak pekerjaan pada saat dijalankan. (Misalnya Anda tidak dapat mengetahui dari sintaks saja apakah "f (x)" adalah panggilan fungsi atau indeks ke dalam array; itu tergantung pada keadaan ruang kerja pada saat run time.) Ini mungkin karena definisi kelas MATLAB diikat ke status filesystem dengan cara yang banyak bahasa lain tidak.

Jadi, apa yang harus dilakukan?

Pendekatan MATLAB idiomatik untuk ini adalah "membuat vektor" kode Anda dengan menyusun definisi kelas Anda sedemikian rupa sehingga instance objek membungkus array; yaitu, masing-masing bidangnya memegang array paralel (disebut organisasi "planar" dalam dokumentasi MATLAB). Alih-alih memiliki array objek, masing-masing dengan bidang memegang nilai skalar, menentukan objek yang merupakan array sendiri, dan meminta metode mengambil array sebagai input, dan membuat panggilan vektor pada bidang dan input. Ini mengurangi jumlah panggilan metode yang dilakukan, semoga cukup bahwa biaya pengiriman bukan hambatan.

Meniru kelas C ++ atau Java di MATLAB mungkin tidak akan optimal. Kelas Java / C ++ biasanya dibangun sedemikian rupa sehingga objek adalah blok bangunan terkecil, sespesifik mungkin (yaitu, banyak kelas berbeda), dan Anda menyusunnya dalam array, koleksi objek, dll, dan beralih di atasnya dengan loop. Untuk membuat kelas MATLAB cepat, putar pendekatan luar ke dalam. Memiliki kelas yang lebih besar yang bidangnya adalah array, dan memanggil metode vektor pada array tersebut.

Intinya adalah untuk mengatur kode Anda untuk bermain dengan kekuatan bahasa - penanganan array, matematika vektor - dan menghindari titik lemah.

EDIT: Sejak posting asli, R2010b dan R2011a telah keluar. Gambaran keseluruhannya sama, dengan panggilan MCOS sedikit lebih cepat, dan Java dan panggilan metode lama semakin lambat .

EDIT: Saya dulu memiliki beberapa catatan di sini pada "sensitivitas jalur" dengan tabel tambahan waktu panggilan fungsi, di mana waktu fungsi dipengaruhi oleh bagaimana jalur Matlab dikonfigurasi, tapi itu tampaknya merupakan penyimpangan dari pengaturan jaringan khusus saya di waktu. Bagan di atas mencerminkan waktu yang tipikal dari dominannya tes saya dari waktu ke waktu.

Pembaruan: R2011b

EDIT (2/13/2012): R2011b keluar, dan gambar kinerja telah cukup berubah untuk memperbarui ini.

Arch: PCWIN Release: 2011b 
Mesin: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB RAM, NVIDIA NVS 300
Melakukan setiap operasi 100000 kali
gaya total µsec per panggilan
fungsi nop (): 0,01578 0,16
nop (), 10x loop membuka gulungan: 0,01477 0,15
nop (), 100x loop membuka gulungan: 0,01518 0,15
subfungsi nop (): 0,01559 0,16
@ () [] fungsi anonim: 0,06400 0,64
metode nop (obj): 0.28482 2.85
nop () fungsi pribadi: 0,01505 0,15
classdef nop (obj): 0.43323 4.33
classdef obj.nop (): 0.81087 8.11
classdef private_nop (obj): 0.32272 3.23
classdef class.staticnop (): 0.88959 8.90
konstanta classdef: 1.51890 15.19
properti classdef: 0.12992 1.30
properti classdef dengan pengambil: 1.39912 13.99
+ fungsi pkg.nop (): 0.87345 8.73
+ pkg.nop () dari dalam + pkg: 0.80501 8.05
Java obj.nop (): 1.86378 18.64
Java nop (obj): 0.22645 2.26
Java feval ('nop', obj): 0.52544 5.25
Java Klass.static_nop (): 0.35357 3.54
Java obj.nop () dari Java: 0,00010 0,00
MEX mexnop (): 0,08709 0,87
C nop (): 0,00001 0,00
j () (builtin): 0,00251 0,03

Saya pikir hasilnya adalah:

  • Metode MCOS / classdef lebih cepat. Biaya sekarang setara dengan kelas gaya lama, selama Anda menggunakan foo(obj)sintaks. Jadi kecepatan metode tidak lagi menjadi alasan untuk tetap menggunakan kelas gaya lama dalam banyak kasus. (Kudos, MathWorks!)
  • Menempatkan fungsi dalam ruang nama membuatnya lambat. (Bukan baru di R2011b, hanya baru dalam pengujian saya.)

Pembaruan: R2014a

Saya telah merekonstruksi kode pembandingan dan menjalankannya pada R2014a.

Matlab R2014a pada PCWIN64  
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 pada PCWIN64 Windows 7 6.1 (eilonwy-win7) 
Mesin: Core i7-3615QM CPU @ 2.30GHz, 4 GB RAM (Platform Virtual VMware)
nIters = 100000 

Waktu Operasi (µsec)  
fungsi nop (): 0,14 
subfungsi nop (): 0,14 
@ () [] fungsi anonim: 0.69 
Metode nop (obj): 3.28 
nop () fcn pribadi di @class: 0.14 
classdef nop (obj): 5.30 
classdef obj.nop (): 10.78 
classdef pivate_nop (obj): 4.88 
classdef class.static_nop (): 11.81 
konstanta classdef: 4.18 
properti classdef: 1.18 
properti classdef dengan pengambil: 19.26 
+ pkg.nop () fungsi: 4.03 
+ pkg.nop () dari dalam + pkg: 4.16 
feval ('nop'): 2.31 
feval (@nop): 0,22 
eval ('nop'): 59.46 
Java obj.nop (): 26.07 
Java nop (obj): 3.72 
Java feval ('nop', obj): 9.25 
Java Klass.staticNop (): 10.54 
Java obj.nop () dari Java: 0,01 
MEX mexnop (): 0,91 
builtin j (): 0,02 
akses lapangan struct s.foo: 0.14 
isempty (persistent): 0.00 

Pembaruan: R2015b: Objek jadi lebih cepat!

Inilah hasil R2015b, yang disediakan oleh @Shaked. Ini adalah perubahan besar : OOP secara signifikan lebih cepat, dan sekarang obj.method()sintaksisnya secepat method(obj), dan jauh lebih cepat daripada objek OOP sebelumnya.

Matlab R2015b pada PCWIN64  
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 pada PCWIN64 Windows 8 6.2 (nanit-shaked) 
Mesin: Core i7-4720HQ CPU @ 2.60GHz, 16 GB RAM (20378)
nIters = 100000 

Waktu Operasi (µsec)  
fungsi nop (): 0,04 
subfungsi nop (): 0,08 
@ () [] fungsi anonim: 1,83 
metode nop (obj): 3.15 
nop () fcn pribadi di @class: 0,04 
classdef nop (obj): 0.28 
classdef obj.nop (): 0,31 
classdef pivate_nop (obj): 0.34 
classdef class.static_nop (): 0,05 
konstanta classdef: 0.25 
properti classdef: 0,25 
properti classdef dengan pengambil: 0.64 
+ pkg.nop () fungsi: 0,04 
+ pkg.nop () dari dalam + pkg: 0,04 
feval ('nop'): 8.26 
feval (@nop): 0,63 
eval ('nop'): 21.22 
Java obj.nop (): 14.15 
Java nop (obj): 2.50 
Java feval ('nop', obj): 10.30 
Java Klass.staticNop (): 24.48 
Java obj.nop () dari Java: 0,01 
MEX mexnop (): 0,33 
builtin j (): 0,15 
akses lapangan struct s.foo: 0.25 
isempty (persistent): 0.13 

Pembaruan: R2018a

Inilah hasil R2018a. Ini bukan lompatan besar yang kami lihat ketika mesin eksekusi baru diperkenalkan di R2015b, tapi ini masih merupakan peningkatan yang cukup berarti dari tahun ke tahun. Khususnya, fungsi anonim menangani lebih cepat.

Matlab R2018a pada MACI64  
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 pada MACI64 Mac OS X 10.13.5 (eilonwy) 
Mesin: Core i7-3615QM CPU @ 2.30GHz, 16 GB RAM 
nIters = 100000 

Waktu Operasi (µsec)  
fungsi nop (): 0,03 
subfungsi nop (): 0,04 
@ () [] fungsi anonim: 0.16 
classdef nop (obj): 0.16 
classdef obj.nop (): 0,17 
classdef pivate_nop (obj): 0.16 
classdef class.static_nop (): 0,03 
konstanta classdef: 0.16 
properti classdef: 0,13 
properti classdef dengan pengambil: 0.39 
+ pkg.nop () fungsi: 0,02 
+ pkg.nop () dari dalam + pkg: 0,02 
feval ('nop'): 15.62 
feval (@nop): 0,43 
eval ('nop'): 32.08 
Java obj.nop (): 28.77 
Java nop (obj): 8.02 
Java feval ('nop', obj): 21.85 
Java Klass.staticNop (): 45.49 
Java obj.nop () dari Java: 0,03 
MEX mexnop (): 3,54 
builtin j (): 0,10 
akses lapangan struct s.foo: 0.16 
isempty (persistent): 0,07 

Pembaruan: R2018b dan R2019a: Tidak ada perubahan

Tidak ada perubahan signifikan. Saya tidak repot memasukkan hasil tes.

Kode Sumber untuk Tolok Ukur

Saya telah memasukkan kode sumber untuk benchmark ini di GitHub, dirilis di bawah Lisensi MIT. https://github.com/apjanke/matlab-bench

Andrew Janke
sumber
5
@AndrewJanke Apakah Anda pikir Anda bisa menjalankan benchmark lagi dengan R2012a? Ini sangat menarik.
Dang Khoa
7
Hai teman-teman. Jika Anda masih tertarik dengan kode sumber, saya telah merekonstruksi dan membuka-sumbernya di GitHub. github.com/apjanke/matlab-bench
Andrew Janke
2
@Eeda: Metode statis terdaftar sebagai "classdef class.static_nop ()" dalam hasil ini. Mereka cukup lambat dibandingkan dengan fungsi. Jika mereka tidak sering dipanggil, itu tidak masalah.
Andrew Janke
2
@AndrewJanke Ini dia: gist.github.com/ShakedDovrat/62db9e8f6883c5e28fc0
Shaked
2
Wow! Jika hasil itu bertahan, saya mungkin perlu merevisi seluruh jawaban ini. Ditambahkan. Terima kasih!
Andrew Janke
3

Kelas pegangan memiliki overhead tambahan dari pelacakan semua referensi ke dirinya sendiri untuk tujuan pembersihan.

Coba percobaan yang sama tanpa menggunakan kelas pegangan dan lihat apa hasil Anda.

MikeEL
sumber
1
persis percobaan yang sama dengan String, tetapi sekarang sebagai kelas nilai (pada komputer lain sekalipun); atest: 0,009, btest: o.356. Itu pada dasarnya perbedaan yang sama dengan pegangan, jadi saya tidak berpikir melacak referensi adalah jawaban kuncinya. Itu juga tidak menjelaskan overhead dalam fungsi vs fungsi dalam paket.
stijn
Matlab versi apa yang Anda gunakan?
MikeEL
1
Saya sudah menjalankan beberapa perbandingan serupa antara kelas pegangan dan nilai dan belum melihat perbedaan kinerja antara keduanya.
RjOllos
Saya tidak lagi melihat perbedaan.
MikeEL
Masuk akal: di Matlab, semua array, tidak hanya menangani objek, dihitung referensi, karena mereka menggunakan copy-on-write dan berbagi data mentah yang mendasarinya.
Andrew Janke
1

Kinerja OO sangat tergantung pada versi MATLAB yang digunakan. Saya tidak dapat mengomentari semua versi, tetapi tahu dari pengalaman bahwa 2012a jauh lebih baik dari versi 2010. Tidak ada tolok ukur dan tidak ada angka untuk ditampilkan. Kode saya, secara eksklusif ditulis menggunakan kelas pegangan dan ditulis di bawah 2012a tidak akan berjalan sama sekali di bawah versi sebelumnya.

HG Bruce
sumber
1

Sebenarnya tidak ada masalah dengan kode Anda, tetapi itu adalah masalah dengan Matlab. Saya pikir di dalamnya adalah semacam bermain-main agar terlihat seperti. Tidak lebih dari overhead untuk mengkompilasi kode kelas. Saya telah melakukan tes dengan titik kelas sederhana (sekali sebagai pegangan) dan yang lainnya (sekali sebagai kelas nilai)

    classdef Pointh < handle
    properties
       X
       Y
    end  
    methods        
        function p = Pointh (x,y)
            p.X = x;
            p.Y = y;
        end        
        function  d = dist(p,p1)
            d = (p.X - p1.X)^2 + (p.Y - p1.Y)^2 ;
        end

    end
end

ini tesnya

%handle points 
ph = Pointh(1,2);
ph1 = Pointh(2,3);

%values  points 
p = Pointh(1,2);
p1 = Pointh(2,3);

% vector points
pa1 = [1 2 ];
pa2 = [2 3 ];

%Structur points 
Ps.X = 1;
Ps.Y = 2;
ps1.X = 2;
ps1.Y = 3;

N = 1000000;

tic
for i =1:N
    ph.dist(ph1);
end
t1 = toc

tic
for i =1:N
    p.dist(p1);
end
t2 = toc

tic
for i =1:N
    norm(pa1-pa2)^2;
end
t3 = toc

tic
for i =1:N
    (Ps.X-ps1.X)^2+(Ps.Y-ps1.Y)^2;
end
t4 = toc

Hasil t1 =

12,0212% Menangani

t2 =

Nilai 12,0042%

t3 =

0.5489  % vector

t4 =

0.0707 % structure 

Oleh karena itu untuk kinerja yang efisien, hindari penggunaan OOP sebagai gantinya struktur adalah pilihan yang baik untuk mengelompokkan variabel

Ahmad
sumber