Is MATLAB OOP traag of doe ik iets verkeerd?

Ik experimenteer met MATLABOOP, om te beginnen heb ik de Logger-klassen van C++ nagebootst en ik zet al mijn string-helperfuncties in een String-klasse, denkend dat het geweldig zou zijn om in plaats daarvan dingen als a + b, a == b, a.find( b )te kunnen doen
van strcat( a b ), strcmp( a, b ), haal het eerste element van strfind( a, b )op, etc.

Het probleem: vertraging

Ik heb de bovenstaande dingen gebruikt en merkte meteen een drastischevertraging op. Doe ik het verkeerd (wat zeker mogelijk is omdat ik nogal beperkte MATLAB-ervaring heb), of introduceert MATLAB’s OOP gewoon veel overhead?

Mijn testcase

Hier is de eenvoudige test die ik deed voor string, eigenlijk gewoon een string toevoegen en het toegevoegde deel weer verwijderen:

Opmerking: schrijf een String-klasse zoals deze niet in echte code! Matlab heeft nu een native stringarray-type, en je zou dat in plaats daarvan moeten gebruiken.

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' );

De resultaten

Totale tijd in seconden, voor 1000 herhalingen:

btest 0,550 (met String.SetLength 0.138, String.plus 0.065, String.Length 0.057)

test 0,015

Resultaten voor het loggersysteem zijn eveneens: 0,1 seconde voor 1000 oproepen
to frpintf( 1, 'test\n' ), 7 (!) seconden voor 1000 aanroepen naar mijn systeem bij intern gebruik van de String-klasse (OK, er zit veel meer logica in, maar om vergelijk met C++: de overhead van mijn systeem dat gebruikmaakt van std::string( "blah" )en std::coutaan de uitvoerzijde versus gewoon std::cout << "blah"is in de orde van 1 milliseconde.)

Is het gewoon overhead bij het opzoeken van klasse-/pakketfuncties?

Omdat MATLAB wordt geïnterpreteerd, moet het tijdens runtime de definitie van een functie/object opzoeken. Dus ik vroeg me af dat er misschien veel meer overhead is betrokken bij het opzoeken van klasse- of pakketfunctie versus functies die zich op het pad bevinden. Ik heb geprobeerd dit te testen, en het wordt alleen maar vreemder. Om de invloed van klassen/objecten uit te sluiten, vergeleek ik het aanroepen van een functie in het pad versus een functie in een pakket:

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

Resultaten, verzameld op dezelfde manier als hierboven:

test 0,004 sec, 0,001 sec in ctest

btest 0,060 sec, 0,014 sec in util.ctest

Dus, komt al deze overhead gewoon van MATLAB die tijd besteedt aan het opzoeken van definities voor de OOP-implementatie, terwijl deze overhead er niet is voor functies die direct in het pad zitten?


Antwoord 1, autoriteit 100%

Ik werk al een tijdje met OO MATLAB en heb uiteindelijk vergelijkbare prestatieproblemen ondervonden.

Het korte antwoord is: ja, MATLAB’s OOP is nogal traag. Er is een aanzienlijke overhead voor methodeaanroepen, hoger dan de reguliere OO-talen, en u kunt er niet veel aan doen. Een deel van de reden kan zijn dat idiomatische MATLAB “gevectoriseerde” code gebruikt om het aantal methodeaanroepen te verminderen, en overhead per aanroep heeft geen hoge prioriteit.

Ik heb de prestaties gebenchmarkt door niets-doende “nop”-functies te schrijven als de verschillende soorten functies en methoden. Hier zijn enkele typische resultaten.

>> call_nops
Computer: PCWIN Release: 2009b
Elke functie/methode 100000 keer aanroepen
nop() functie: 0,02261 sec 0,23 usec per aanroep
nop1-5() functies: 0,02182 sec 0,22 usec per oproep
nop() subfunctie: 0,02244 sec 0,22 usec per aanroep
@()[] anonieme functie: 0,08461 sec 0,85 usec per aanroep
nop(obj) methode: 0.24664 sec 2.47 usec per aanroep
nop1-5 (obj) methoden: 0,23469 sec 2,35 usec per aanroep
nop() privéfunctie: 0,02197 sec 0,22 usec per aanroep
classdef nop(obj): 0,90547 sec 9,05 usec per oproep
classdef obj.nop(): 1.75522 sec 17.55 usec per aanroep
classdef private_nop(obj): 0,84738 sec 8,47 usec per oproep
classdef nop(obj) (m-bestand): 0,90560 sec 9,06 usec per oproep
classdef class.staticnop(): 1.16361 sec 11.64 usec per aanroep
Java nop(): 2,43035 sec 24,30 usec per aanroep
Java static_nop(): 0,87682 sec 8,77 usec per aanroep
Java nop() van Java: 0.00014 sec 0.00 usec per aanroep
MEX mexnop(): 0,11409 sec 1,14 usec per gesprek
C nop(): 0.00001 sec 0.00 usec per gesprek

Vergelijkbare resultaten op R2008a tot en met R2009b. Dit is op Windows XP x64 met 32-bit MATLAB.

De “Java nop()” is een niets-doende Java-methode die wordt aangeroepen vanuit een M-code-lus, en omvat de MATLAB-naar-Java-verzendoverhead bij elke aanroep. “Java nop() van Java” is hetzelfde dat in een Java for()-lus wordt genoemd en die grensstraf niet oplevert. Neem de Java- en C-timings met een korreltje zout; een slimme compiler zou de oproepen volledig kunnen optimaliseren.

Het pakket scoping-mechanisme is nieuw, geïntroduceerd op ongeveer hetzelfde moment als de classdef-klassen. Het gedrag kan gerelateerd zijn.

Een paar voorlopige conclusies:

  • Methoden zijn langzamer dan functies.
  • Nieuwe stijl (classdef) methoden zijn langzamer dan oude stijl methoden.
  • De nieuwe obj.nop()-syntaxis is langzamer dan de nop(obj)-syntaxis, zelfs voor dezelfde methode op een classdef-object. Hetzelfde geldt voor Java-objecten (niet getoond). Als je snel wilt gaan, bel dan nop(obj).
  • Overhead voor methodeaanroepen is hoger (ongeveer 2x) in 64-bits MATLAB op Windows. (Niet weergegeven.)
  • De verzending van de MATLAB-methode is langzamer dan in sommige andere talen.

Zeggen waarom dit zo is, zou slechts speculatie van mijn kant zijn. De OO internals van de MATLAB-engine zijn niet openbaar. Het is niet per se een geïnterpreteerd versus gecompileerd probleem – MATLAB heeft een JIT – maar MATLAB’s lossere typering en syntaxis kan meer werk betekenen tijdens runtime. (Je kunt bijvoorbeeld niet alleen aan de hand van de syntaxis zien of “f(x)” een functieaanroep is of een index in een array; het hangt af van de status van de werkruimte tijdens runtime.) Het kan zijn dat de klassendefinities van MATLAB gebonden zijn naar bestandssysteemstatus op een manier die veel andere talen niet zijn.

Dus, wat te doen?

Een idiomatische MATLAB-benadering hiervoor is om uw code te “vectoriseren” door uw klassedefinities zo te structureren dat een objectinstantie een array omhult; dat wil zeggen dat elk van zijn velden parallelle arrays bevat (in de MATLAB-documentatie “planaire” organisatie genoemd). In plaats van een array van objecten te hebben, elk met velden die scalaire waarden bevatten, definieer je objecten die zelf arrays zijn, en laat je de methoden arrays als invoer nemen en gevectoriseerde aanroepen doen op de velden en invoer. Dit vermindert het aantal gemaakte methodeaanroepen, hopelijk voldoende om de verzendingsoverhead geen bottleneck te laten vormen.

Het nabootsen van een C++- of Java-klasse in MATLAB is waarschijnlijk niet optimaal. Java/C++-klassen zijn meestal zo gebouwd dat objecten de kleinste bouwstenen zijn, zo specifiek als je kunt (dat wil zeggen, veel verschillende klassen), en je stelt ze samen in arrays, verzamelingsobjecten, enz., en herhaalt ze met lussen. Om snelle MATLAB-lessen te maken, moet je die aanpak binnenstebuiten keren. Zorg voor grotere klassen waarvan de velden arrays zijn, en roep gevectoriseerde methoden op die arrays aan.

Het gaat erom je code zo in te richten dat deze wordt afgespeeld met de sterke punten van de taal – array-handling, gevectoriseerde wiskunde – en de zwakke plekken vermijdt.

EDIT: sinds het oorspronkelijke bericht zijn R2010b en R2011a uitgekomen. Het algemene beeld is hetzelfde, met MCOS-aanroepen die een beetje sneller worden en Java- en ouderwetse methodeaanroepen die tragerworden.

EDIT: ik had hier enkele opmerkingen over “padgevoeligheid” met een extra tabel met timings van functieaanroepen, waarbij functietijden werden beïnvloed door hoe het Matlab-pad was geconfigureerd, maar dat lijkt een afwijking te zijn van mijn specifieke netwerkconfiguratie op dat moment. De bovenstaande grafiek geeft de tijden weer die typerend zijn voor het overwicht van mijn tests in de loop van de tijd.

Update: R2011b

EDIT (2/13/2012): R2011b is uit en het prestatiebeeld is voldoende veranderd om dit bij te werken.

Boog: PCWIN-release: 2011b
Machine: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB RAM, NVIDIA NVS 300
Elke bewerking 100000 keer uitvoeren
stijl totaal µsec per gesprek
nop() functie: 0.01578 0.16
nop(), 10x lus uitrollen: 0.01477 0.15
nop(), 100x lus uitrollen: 0.01518 0.15
nop() subfunctie: 0.01559 0.16
@()[] anonieme functie: 0,06400 0,64
nop (obj) methode: 0.28482 2.85
nop() privéfunctie: 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
classdef-constante: 1.51890 15.19
classdef-eigenschap: 0.12992 1.30
classdef eigenschap met getter: 1.39912 13.99
+pkg.nop() functie: 0.87345 8.73
+pkg.nop() van binnenuit +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() van Java: 0.00010 0.00
MEX mexnop(): 0.08709 0.87
C nop(): 0.00001 0.00
j() (ingebouwd): 0.00251 0.03

Ik denk dat het resultaat hiervan is dat:

  • MCOS/classdef-methoden zijn sneller. De kosten zijn nu ongeveer gelijk aan die van klassen in oude stijl, zolang je de syntaxis foo(obj)gebruikt. Dus de snelheid van de methode is in de meeste gevallen geen reden meer om bij de klassen in oude stijl te blijven. (Plof, MathWorks!)
  • Het plaatsen van functies in naamruimten maakt ze traag. (Niet nieuw in R2011b, alleen nieuw in mijn test.)

Update: R2014a

Ik heb de benchmarkingcode gereconstrueerd en uitgevoerd op R2014a.

Matlab R2014a op PCWIN64
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 op PCWIN64 Windows 7 6.1 (eilonwy-win7)
Machine: Core i7-3615QM CPU @ 2,30GHz, 4 GB RAM (VMware Virtual Platform)
nIters = 100000
Bedrijfstijd (µsec)
nop() functie: 0.14
nop() subfunctie: 0.14
@()[] anonieme functie: 0.69
nop (obj) methode: 3.28
nop() privé fcn op @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
classdef-constante: 4.18
classdef-eigenschap: 1.18
classdef eigenschap met getter: 19.26
+pkg.nop() functie: 4.03
+pkg.nop() van binnenuit +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 () van Java: 0.01
MEX MEXNOP (): 0.91
Gebouwd J (): 0.02
Struct S.FOO Veldtoegang: 0.14
isvermeld (persistent): 0,00

UPDATE: R2015B: Objecten werden sneller!

Hier zijn R2015B-resultaten, vriendelijk geleverd door @shaked. Dit is een groot verandering: OOP is significant sneller, en nu is de obj.method()syntaxis zo snel als method(obj), en veel sneller dan legacy OOP-objecten.

Matlab R2015B op PCWIN64
MATLAB 8.6.0.267246 (R2015B) / JAVA 1.7.0_60 op PCWIN64 Windows 8 6.2 (Nanit-geschuddt)
Machine: Core I7-4720HQ CPU @ 2.60GHz, 16 GB RAM (20378)
niters = 100000
Werktijd (μsec)
NOP () Functie: 0,04
NOP () SUBFUCTIES: 0.08
@ () [] Anonieme functie: 1.83
NOP (OBJ) METHODE: 3.15
NOP () Privé FCN op @class: 0.04
Classdef NOP (OBJ): 0.28
Classdef obj.nop (): 0.31
Classdef PIVATE_NOP (OBJ): 0.34
ClassDef Class.Stic_nop (): 0.05
CLASSDEF Constant: 0,25
CLASSDEF-object: 0,25
CLASSDEF-eigenschap met Getter: 0.64
+ PKG.NOP () Functie: 0.04
+ PKG.NOP () van binnenkant + 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 uur
Java klass.staticnop (): 24.48
Java obj.nop () van Java: 0.01
MEX MEXNOP (): 0.33
Gebouwd J (): 0.15
Struct S.FOO Veldtoegang: 0,25
isvermeld (persistent): 0.13

UPDATE: R2018A

Hier is R2018A-resultaten. Het is niet de enorme sprong die we zagen toen de nieuwe uitvoeringsmotor in R2015B werd geïntroduceerd, maar het is nog steeds een aanzienlijk jaarverbetering. Met name de handgrepen van anonieme functie kreeg veel sneller.

Matlab R2018A op Maci64
MATLAB 9.4.0.813654 (R2018A) / JAVA 1.8.0_144 op Maci64 Mac OS X 10.13.5 (Eilonwy)
Machine: Core I7-3615QM CPU @ 2.30 GHz, 16 GB RAM
niters = 100000
Werktijd (μsec)
NOP () Functie: 0.03
NOP () subfunctie: 0,04
@ () [] Anonieme functie: 0.16
Classdef NOP (OBJ): 0.16
Classdef obj.nop (): 0.17
ClassDef PIVATE_NOP (OBJ): 0.16
ClassDef Class.Stic_nop (): 0.03
CLASSDEF Constant: 0.16
CLASSDEF-eigenschap: 0.13
CLASSDEF-eigenschap met Getter: 0.39
+ PKG.NOP () Functie: 0.02
+ PKG.NOP () van binnenuit + 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 () van Java: 0.03
MEX MEXNOP (): 3.54
Gebouwd J (): 0.10
Struct S.FOO Veldtoegang: 0.16
isvermeld (persistent): 0,07

UPDATE: R2018B en R2019A: GEEN VERANDERING

Geen significante wijzigingen. Ik stoor niet om de testresultaten op te nemen.

Update: R2021A: Zelfs snellere objecten!

Lijkt erop dat Classdef-objecten weer aanzienlijk sneller zijn geworden. Maar structuren zijn langzamer geworden.

Matlab R2021A op Maci64
MATLAB 9.10.0.1669831 (R2021A) Update 2 / JAVA 1.8.0_202 op Maci64 Mac OS X 10.14.6 (Eilonwy) 
Machine: Core i7-3615QM CPU @ 2.30GHz, 4 cores, 16 GB RAM
nIters = 100000
Bedrijfstijd (μsec)
nop() functie: 0.03
nop() subfunctie: 0.04
@()[] anonieme functie: 0.14
nop (obj) methode: 6.65
nop() privé fcn op @class: 0.02
classdef nop (obj): 0.03
classdef obj.nop(): 0.04
classdef pivate_nop(obj): 0.03
classdef class.static_nop(): 0.03
classdef-constante: 0.16
classdef-eigenschap: 0.12
classdef eigenschap met getter: 0.17
+pkg.nop() functie: 0.02
+pkg.nop() van binnenuit +pkg: 0.02
feval('nop'): 14.45
feval(@nop): 0,59
eval('nop'): 23.59
Java obj.nop(): 30.01
Java nop(obj): 6.80
Java feval('nop',obj): 18.17
Java Klass.staticNop(): 16.77
Java obj.nop() van Java: 0.02
MEX mexnop(): 2.51
ingebouwde j(): 0.21
struct s.foo veldtoegang: 0.29
isleeg (aanhoudend): 0.26

Broncode voor benchmarks

Ik heb de broncode voor deze benchmarks op GitHub gezet, vrijgegeven onder de MIT-licentie. https://github.com/apjanke/matlab-bench


Antwoord 2, autoriteit 2%

De handle-klasse heeft een extra overhead door alle verwijzingen naar zichzelf te volgen voor opschoningsdoeleinden.

Probeer hetzelfde experiment zonder de handle class te gebruiken en kijk wat uw resultaten zijn.


Antwoord 3

OO-prestaties zijn sterk afhankelijk van de gebruikte MATLAB-versie. Ik kan niet op alle versies reageren, maar weet uit ervaring dat 2012a veel verbeterd is ten opzichte van de 2010-versies. Geen benchmarks en dus geen cijfers om te presenteren. Mijn code, die uitsluitend is geschreven met handle-klassen en is geschreven onder 2012a, werkt helemaal niet onder eerdere versies.


Antwoord 4

Eigenlijk geen probleem met uw code, maar het is een probleem met Matlab. Ik denk dat het een soort van spelen is om eruit te zien. Het is niets dan overhead om de klassencode te compileren.
Ik heb de test gedaan met een eenvoudig klassepunt (eenmaal als handvat) en de andere (eenmaal als waardeklasse)

   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

hier is de test

%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

De resultaten
t1 =

12.0212 % Handvat

t2 =

12.0042 % waarde

t3 =

0.5489  % vector

t4 =

0.0707 % structure 

Daarom voor efficiënte prestaties vermijd het gebruik van OOP, in plaats daarvan is structuur een goede keuze om variabelen te groeperen

Other episodes