MATLAB OOP повільний чи я щось роблю не так?


144

Я експериментував з MATLAB об'єктно - орієнтованого програмування , як почати я імітував мій С ++ класів Logger і я розміщую всі мої рядки допоміжні функції в класі струнних, думаючи , що це було б здорово , щоб бути в змозі робити такі речі , як a + b, a == b, a.find( b )замість того strcat( a b ), strcmp( a, b ), отримати перший елемент strfind( a, b )і т.д.

Проблема: уповільнення

Я поставив вищезазначені речі для використання і одразу помітив різке уповільнення. Я роблю це неправильно (що, безумовно, можливо, оскільки я маю досить обмежений досвід MATLAB), або OOP MATLAB просто вводить багато накладних витрат?

Мій тестовий випадок

Ось простий тест, який я зробив для рядка, в основному просто додавши рядок і знову видаливши додану частину:

Примітка: насправді не пишіть такий клас String в реальному коді! Matlab має stringтип власного масиву зараз, і вам слід скористатися цим.

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

Результати

Загальний час у секундах, за 1000 ітерацій:

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

atest 0,015

Результати для системи реєстратора аналогічні: 0,1 секунди для 1000 дзвінків до frpintf( 1, 'test\n' ), 7 (!) Секунд за 1000 дзвінків до моєї системи при внутрішньому використанні класу String (Гаразд, у ньому набагато більше логіки, але для порівняння з C ++: накладні витрати в моїй системі, яка використовує std::string( "blah" )і std::coutна вихідній стороні проти звичайної, std::cout << "blah"знаходиться в порядку 1 мілісекунда.)

Це просто накладні витрати під час пошуку функцій класу / пакета?

Оскільки MATLAB інтерпретується, він повинен шукати визначення функції / об'єкта під час виконання. Тож мені було цікаво, що, можливо, набагато більше накладних витрат бере участь у пошуку класу або функції пакета проти функцій, які знаходяться на шляху. Я спробував це перевірити, і це стає незнайомим. Щоб виключити вплив класів / об'єктів, я порівнював виклик функції в шляху проти функції в пакеті:

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

Результати, зібрані так само, як вище:

atest 0,004 сек, 0,001 сек в ctest

btest 0,060 сек, 0,014 сек в util.ctest

Отже, чи все це накладні витрати лише відбувається від того, що MATLAB витрачає час на пошук визначень для його реалізації OOP, тоді як цей накладний обсяг не існує для функцій, які знаходяться безпосередньо на шляху?


5
Дякую за це запитання! Продуктивність купи Matlab (OOP / закриття) мене турбує роками, дивіться stackoverflow.com/questions/1446281/matlabs-garbage-collector . Мені дуже цікаво, що відповість MatlabDoug / Loren / MikeKatz на ваш пост.
Михайло

1
^ що було цікавим прочитанням.
Стін

1
@MatlabDoug: можливо, ваш колега Майк Карр може коментувати ОП?
Михайло

4
Читачі також повинні перевірити цю недавню публікацію в блозі (від Дейва Фоті), де обговорюється ефективність роботи OOP в останній версії R2012a: з огляду на ефективність в об'єктно-орієнтованому коді MATLAB
Amro

1
Простий приклад чутливості до структури коду, в якій виклик методів субелементів виводиться з циклу. for i = 1:this.get_n_quantities() if(strcmp(id,this.get_quantity_rlz(i).get_id())) ix = i; end endзаймає 2,2 сек, тоді як 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 endзаймає 0,01, два порядки маг
Хосе Оспіна

Відповіді:


223

Я деякий час працював з OO MATLAB, і, нарешті, переглядав подібні проблеми з продуктивністю.

Коротка відповідь: так, OOP MATLAB - це щось повільно. Існує суттєвий накладний виклик методу, вищий, ніж основні мови OO, і ви не можете з цим зробити багато. Частина причини може полягати в тому, що ідіоматичний MATLAB використовує "векторизований" код для зменшення кількості викликів методів, а накладні витрати на виклик не є великим пріоритетом.

Я оцінив ефективність, написавши функції "nop" "nop" як різні типи функцій та методів. Ось кілька типових результатів.

>> call_nops
Комп'ютер: PCWIN Випуск: 2009b
Виклик кожної функції / методу 100000 разів
функція nop (): 0,02261 сек 0,23 Usec за виклик
nop1-5 () функції: 0,02182 сек 0,22 Usec за виклик
підфункція nop (): 0,02244 сек 0,22 Usec за виклик
@ () [] анонімна функція: 0,08461 сек 0,85 Usec за виклик
метод nop (obj): 0,24664 сек 2,47 Usec за дзвінок
nop1-5 (obj) методи: 0,23469 сек 2,35 Usec за виклик
приватна функція nop (): 0,02197 сек 0,22 Usec за виклик
classdef nop (obj): 0,90547 сек 9,05 Usec за дзвінок
classdef obj.nop (): 1.75522 сек 17.55 Usec за виклик
classdef private_nop (obj): 0,84738 сек 8,47 Usec за виклик
classdef nop (obj) (m-файл): 0,90560 сек 9,06 Usec за виклик
classdef class.staticnop (): 1,16361 сек 11,64 Usec за виклик
Java nop (): 2,43035 сек 24,30 Usec за виклик
Java static_nop (): 0,87682 сек 8,77 Usec за виклик
Java nop () з Java: 0,00014 сек 0,00 Usec за дзвінок
MEX mexnop (): 0.11409 сек 1.14 Usec за виклик
C nop (): 0,00001 сек 0,00 Usec за дзвінок

Аналогічні результати на R2008a через R2009b. Це на Windows XP x64 з 32-бітним MATLAB.

"Java nop ()" - це метод нічого не робиться Java, що викликається з циклу M-коду, і включає в себе диспетчер команди MATLAB-Java накладні з кожним викликом. "Java nop () від Java" - це те саме, що називається в циклі Java for () і не несе цього граничного покарання. Візьміть таймінги Java та C із зерном солі; розумний компілятор може повністю оптимізувати виклики.

Механізм оцінювання пакетів є новим, введений приблизно в той же час, що і класи classdef. Його поведінка може бути пов'язаною.

Кілька попередніх висновків:

  • Методи повільніші, ніж функції.
  • Методи нового стилю (classdef) більш повільні, ніж методи старого стилю.
  • Новий obj.nop()синтаксис повільніше, ніж nop(obj)синтаксис, навіть для того ж методу на об’єкті classdef. Те саме для об’єктів Java (не показано). Якщо ви хочете швидко поїхати, зателефонуйте nop(obj).
  • Накладні накладні виклику методу вище (приблизно в 2 рази) у 64-розрядних MATLAB в Windows. (Не показаний.)
  • Відправлення методу MATLAB відбувається повільніше, ніж деякі інші мови.

Сказати, чому це так, було б просто міркуванням з мого боку. Внутрішні положення OO двигуна MATLAB не є загальнодоступними. Це не інтерпретується проти компільованого випуску як такого - MATLAB має JIT - але більш друге введення та синтаксис MATLAB може означати більше роботи під час виконання. (Наприклад, ви не можете сказати з синтаксису самостійно, "f (x)" - це виклик функції або індекс в масив; це залежить від стану робочої області під час виконання.) Це може бути тому, що визначення класу MATLAB пов'язані у стан файлової системи таким чином, що багато інших мов 'не є.

Отже, що робити?

Ідіоматичний підхід MATLAB до цього полягає в "векторизації" вашого коду, структурувавши визначення вашого класу таким чином, що екземпляр об'єкта обертає масив; тобто кожне його поле містить паралельні масиви (в документації MATLAB називається "планарною" організацією). Замість того, щоб мати масив об'єктів, кожне з полями, що містять скалярні значення, визначає об'єкти, які самі є масивами, і має методи приймати масиви як входи та робити векторизовані дзвінки на поля та входи. Це зменшує кількість здійснених викликів методів, сподіваємось, що накладні витрати не є вузьким місцем.

Імітація класу C ++ або Java в MATLAB, ймовірно, не буде оптимальною. Класи Java / C ++, як правило, будуються таким чином, що об'єкти є найменшими будівельними блоками, настільки специфічними, як ви можете (тобто, безліччю різних класів), і ви складаєте їх у масиви, об'єкти колекції тощо та повторюєте їх за допомогою циклів. Щоб робити швидкі класи MATLAB, перетворіть цей підхід назовні. Майте великі класи, поля яких є масивами, і викликайте векторизовані методи на цих масивах.

Сенс полягає в тому, щоб організувати свій код, щоб він відповідав сильним мовам - обробці масивів, векторизованій математиці - і уникати слабких місць.

EDIT: З початкової публікації R2010b та R2011a вийшли. Загальна картина однакова, тому що дзвінки MCOS стають трохи швидшими, а дзвінки Java та старі стилі - повільніше .

EDIT: У мене раніше були деякі примітки про "чутливість до тракту" з додатковою таблицею часових викликів функцій, де на час функціонування впливало те, як налаштовано шлях Matlab, але, схоже, це було відхиленням моєї конкретної настройки мережі на час. Діаграма вище відображає часи, характерні для переваги моїх тестів у часі.

Оновлення: R2011b

EDIT (13.02.2012): R2011b вимкнений, а зображення продуктивності досить змінилося, щоб оновити це.

Арка: PCWIN Випуск: 2011b 
Машина: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 Гб оперативної пам’яті, NVIDIA NVS 300
Робити кожну операцію 100000 разів
загальний стиль мксек за виклик
функція nop (): 0,01578 0,16
nop (), розгортання циклу 10x: 0,01477 0,15
nop (), розгортання циклу 100x: 0,01518 0,15
підфункція nop (): 0,01559 0,16
@ () [] анонімна функція: 0,06400 0,64
метод nop (obj): 0,28482 2,85
nop () приватна функція: 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: 1,51890 15,19
майно classdef: 0.12992 1.30
майно classdef з Getter: 1.39912 13.99
+ функція pkg.nop (): 0,87345 8,73
+ pkg.nop () зсередини + 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 () від Java: 0,00010 0,00
MEX mexnop (): 0,08709 0,87
C nop (): 0,00001 0,00
j () (вбудований): 0,00251 0,03

Я думаю, що результат цього полягає в тому, що:

  • Методи MCOS / classdef швидші. Вартість зараз приблизно дорівнює старим класам стилів, якщо ви використовуєте foo(obj)синтаксис. Тому швидкість методу вже не є причиною дотримуватися класів старовинних стилів у більшості випадків. (Кудо, MathWorks!)
  • Введення функцій у простори імен робить їх повільними. (Не новий у R2011b, просто новий у моєму тесті.)

Оновлення: R2014a

Я реконструював код бенчмаркінгу і запустив його на R2014a.

Matlab R2014a на PCWIN64  
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 на PCWIN64 Windows 7 6.1 (eilonwy-win7) 
Машина: Core i7-3615QM CPU при 2,30 ГГц, 4 ГБ оперативної пам’яті (віртуальна платформа VMware)
nIters = 100000 

Час роботи (мкз)  
функція nop (): 0,14 
підфункція nop (): 0,14 
@ () [] анонімна функція: 0,69 
метод nop (obj): 3.28 
nop () приватний fcn на @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: 4.18 
властивість classdef: 1.18 
майно classdef з Getter: 19.26 
+ функція pkg.nop (): 4.03 
+ pkg.nop () зсередини + 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 () від Java: 0.01 
MEX mexnop (): 0,91 
вбудований j (): 0,02 
Доступ до польових структур в межах: 0.14 
порожній (стійкий): 0,00 

Оновлення: R2015b: Об'єкти стали швидшими!

Ось результати R2015b, люб’язно надані @Shaked Це велика зміна: OOP значно швидше, і тепер obj.method()синтаксис швидший method(obj)і набагато швидший, ніж застарілі об'єкти OOP.

Matlab R2015b на PCWIN64  
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 на PCWIN64 Windows 8 6.2 (нанизаний) 
Машина: процесор Core i7-4720HQ при 2,60 ГГц, оперативна пам'ять 16 ГБ (20378)
nIters = 100000 

Час роботи (мкз)  
функція nop (): 0,04 
підфункція nop (): 0,08 
@ () [] анонімна функція: 1,83 
метод nop (obj): 3.15 
nop () приватний fcn на @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 
константа classdef: 0,25 
властивість classdef: 0,25 
майно classdef з Getter: 0,64 
+ функція pkg.nop (): 0,04 
+ pkg.nop () зсередини + 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 () від Java: 0.01 
MEX mexnop (): 0,33 
вбудований j (): 0,15 
Доступ до польових структур Struff: 0,25 
порожній (стійкий): 0,13 

Оновлення: R2018a

Ось результати R2018a Це не той величезний стрибок, який ми побачили, коли новий двигун виконання був представлений в R2015b, але це все ще помітний рік за рік вдосконалення. Помітно, анонімні функції функцій стали швидше.

Matlab R2018a на MACI64  
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 на MACI64 Mac OS X 10.13.5 (eilonwy) 
Машина: процесор Core i7-3615QM при 2,30 ГГц, оперативна пам'ять 16 ГБ 
nIters = 100000 

Час роботи (мкз)  
функція nop (): 0,03 
підфункція nop (): 0,04 
@ () [] анонімна функція: 0,16 
classdef nop (obj): 0,16 
classdef obj.nop (): 0,17 
classdef pivate_nop (obj): 0,16 
classdef class.static_nop (): 0,03 
константа classdef: 0,16 
властивість classdef: 0,13 
майно classdef з getter: 0,39 
+ функція pkg.nop (): 0,02 
+ pkg.nop () зсередини + 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 () від Java: 0,03 
MEX mexnop (): 3,54 
вбудований j (): 0,10 
Доступ до поля Strufo s.foo: 0,16 
isempty (стійкий): 0,07 

Оновлення: R2018b та R2019a: Без змін

Суттєвих змін немає. Я не намагаюся включати результати тесту.

Вихідний код для тестів

Я поставив вихідний код цих орієнтирів на GitHub, випущений під ліцензією MIT. https://github.com/apjanke/matlab-bench


5
@AndrewJanke Як ви думаєте, ви могли б знову запустити тест із R2012a? Це справді цікаво.
Dang Khoa

7
Привіт, люди. Якщо вас все ще цікавить вихідний код, я його реконструював та відкрив на GitHub. github.com/apjanke/matlab-bench
Andrew

2
@Seeda: Статичні методи вказані як "classdef class.static_nop ()" у цих результатах. Вони досить повільні порівняно з функціями. Якщо вони не дзвонять часто, це не має значення.
Ендрю Янке


2
Оце Так! Якщо ці результати відповідають, мені може знадобитися переглянути всю цю відповідь. Додано. Дякую!
Ендрю Янке

3

Клас ручки має додаткові накладні витрати від відстеження всіх посилань на себе для цілей очищення.

Спробуйте той же експеримент, не використовуючи клас ручки, і подивіться, які ваші результати.


1
точно такий же експеримент із String, але зараз як клас цінностей (хоча на іншій машині); atest: 0,009, btest: o.356. Це в основному та ж різниця, що і для ручки, тому я не думаю, що відстеження посилань є ключовою відповіддю. Це також не пояснює накладні витрати у функціях та функції у пакунках.
Стейн

Яку версію математики ви використовуєте?
MikeEL

1
Я провів кілька подібних порівнянь між ручками та класами значень і не помітив різниці в продуктивності між ними.
RjOllos

Я також більше не помічаю різниці.
MikeEL

Має сенс: у Matlab всі масиви, а не лише обробляючі об'єкти, підраховуються, оскільки вони використовують копіювати при записі та ділитися основними необробленими даними.
Ендрю Янке

1

Продуктивність ОО значно залежить від використовуваної версії MATLAB. Я не можу коментувати всі версії, але з досвіду знаю, що 2012a значно покращився порівняно з версіями 2010 року. Ніяких орієнтирів і так ніяких цифр не представляти. Мій код, виключно написаний з використанням класів ручок і написаний під 2012a, взагалі не працюватиме в попередніх версіях.


1

Насправді немає проблеми з вашим кодом, але це проблема з Matlab. Я думаю, що це щось на зразок того, як виглядати. Складати код класу не що інше, як накладні витрати. Я зробив тест з простою точкою класу (один раз як ручка), а інший (один раз як клас значень)

    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

ось тест

%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

Результати t1 =

12.0212% Ручка

t2 =

12.0042% значення

t3 =

0.5489  % vector

t4 =

0.0707 % structure 

Тому для ефективної продуктивності уникайте використання OOP замість структури - хороший вибір для групування змінних

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.