MATLAB OOP работает медленно или я что-то не так делаю?

144

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

Проблема: замедление

Я использовал вышеперечисленные вещи и сразу заметил резкое замедление. Я делаю это неправильно (что, безусловно, возможно, поскольку у меня довольно ограниченный опыт работы с MATLAB), или ООП 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)

тест 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

Результаты, собранные так же, как указано выше:

тест 0,004 с, 0,001 с в тесте

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

Итак, все ли это связано с тем, что MATLAB тратит время на поиск определений для своей реализации ООП, тогда как эти издержки отсутствуют для функций, которые находятся непосредственно на пути?

Стейн
источник
5
Спасибо за этот вопрос! Производительность кучи Matlab (ООП / замыкания) беспокоила меня годами, см. Stackoverflow.com/questions/1446281/matlabs-garbage-collector . Мне действительно любопытно, что MatlabDoug / Loren / MikeKatz ответит на ваш пост.
Михаил
1
^ Это было интересное чтение.
Stijn
1
@MatlabDoug: может быть, ваш коллега Майк Карр может прокомментировать ОП?
Михаил
4
Читатели также должны проверить это недавнее сообщение в блоге (от Дейва Фоти), в котором обсуждается производительность ООП в последней версии 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, два порядка mag
Хосе Оспина

Ответы:

223

Я работал с OO MATLAB некоторое время, и в итоге посмотрел на похожие проблемы с производительностью.

Короткий ответ: да, ООП MATLAB довольно медленный. Это требует значительных накладных расходов на вызов метода, выше, чем у основных языков ОО, и вы ничего не можете с этим поделать. Частично причина может заключаться в том, что идиоматический MATLAB использует «векторизованный» код для уменьшения количества вызовов методов, и издержки на вызов не являются высоким приоритетом.

Я оценил производительность, написав бесполезные «nop» функции как различные типы функций и методов. Вот некоторые типичные результаты.

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

Аналогичные результаты на R2008a через R2009b. Это на Windows XP x64 под управлением 32-битной MATLAB.

«Java nop ()» - это неиспользуемый Java-метод, вызываемый из цикла M-кода, и включает накладные расходы на отправку MATLAB-to-Java при каждом вызове. «Java nop () из Java» - это то же самое, что вызывается в цикле Java for (), и этот штраф за границы не влечет за собой. Возьмите тайминги Java и C с небольшим количеством соли; умный компилятор может полностью оптимизировать вызовы.

Механизм определения объема пакета является новым и представлен примерно в то же время, что и классы classdef. Его поведение может быть связано.

Несколько предварительных выводов:

  • Методы медленнее, чем функции.
  • Методы нового стиля (classdef) работают медленнее, чем методы старого стиля.
  • Новый obj.nop()синтаксис медленнее, чем nop(obj)синтаксис, даже для того же метода объекта classdef. То же самое для объектов Java (не показано). Если хочешь идти быстро, звони nop(obj).
  • Затраты на вызов метода выше (примерно в 2 раза) в 64-битной среде MATLAB в Windows. (Не показаны.)
  • Отправка метода MATLAB медленнее, чем в некоторых других языках.

Сказать, почему это так, было бы спекуляцией с моей стороны. ОО механизма MATLAB не являются публичными. Это не интерпретируемая, а скомпилированная проблема как таковая - MATLAB имеет JIT - но более слабая типизация и синтаксис MATLAB могут означать больше работы во время выполнения. (Например, из одного синтаксиса нельзя определить, является ли «f (x)» вызовом функции или индексом в массиве; это зависит от состояния рабочего пространства во время выполнения.) Это может быть из-за того, что определения классов MATLAB связаны к состоянию файловой системы так, как это делают многие другие языки.

Так что делать?

Идиоматический подход MATLAB к этому состоит в том, чтобы «векторизовать» ваш код, структурируя определения вашего класса таким образом, чтобы экземпляр объекта обернул массив; то есть каждое из его полей содержит параллельные массивы (называемые «планарной» организацией в документации MATLAB). Вместо того, чтобы иметь массив объектов, каждое из которых содержит поля со скалярными значениями, они определяют объекты, которые сами являются массивами, и имеют методы, которые принимают массивы в качестве входных данных и выполняют векторизованные вызовы полей и входных данных. Это уменьшает количество выполненных вызовов методов, надеюсь, что затраты на диспетчеризацию не являются узким местом.

Подражание классу C ++ или Java в MATLAB, вероятно, не будет оптимальным. Классы Java / C ++ обычно создаются таким образом, чтобы объекты были наименьшими строительными блоками, насколько это возможно (то есть, множество различных классов), и вы составляете их в массивы, объекты коллекций и т. Д. И перебираете их с помощью циклов. Чтобы быстро создавать классы MATLAB, выверните этот подход наизнанку. Имейте большие классы, поля которых являются массивами, и вызывайте векторизованные методы для этих массивов.

Суть в том, чтобы ваш код соответствовал сильным сторонам языка - обработке массивов, векторизованной математике - и избегал слабых мест.

РЕДАКТИРОВАТЬ: начиная с оригинального сообщения, R2010b и R2011a вышли. Общая картина такая же: вызовы MCOS становятся немного быстрее, а вызовы методов Java и старого стиля - медленнее .

РЕДАКТИРОВАТЬ: Раньше у меня были некоторые заметки о «чувствительности пути» с дополнительной таблицей времени вызовов функций, где время функций зависело от того, как был настроен путь Matlab, но, похоже, это было отклонением от моей конкретной настройки сети в время. Приведенная выше таблица отражает время, типичное для большинства моих тестов с течением времени.

Обновление: R2011b

РЕДАКТИРОВАТЬ (13.02.2012): R2011b отсутствует, и картина производительности изменилась достаточно, чтобы обновить это.

Арка: PCWIN Релиз: 2011b 
Машина: R2011b, Windows XP, 8x Core i7-2600 @ 3.40 ГГц, 3 ГБ оперативной памяти, NVIDIA NVS 300
Делая каждую операцию 100000 раз
общий стиль, мкс на звонок
Функция nop (): 0,01578 0,16
nop (), 10-кратное развертывание цикла: 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
постоянная класса: 1.51890 15.19
Свойство classdef: 0,12992 1,30
Свойство classdef с геттером: 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 
константа класса: 4.18 
Свойство classdef: 1,18 
Свойство classdef с геттером: 19.26 
Функция + pkg.nop (): 4,03 
+ pkg.nop () изнутри + pkg: 4.16 
feval ('nop'): 2,31 
февал (@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 
Доступ к полю struct s.foo: 0,14 
isempty (постоянный): 0,00 

Обновление: R2015b: Объекты стали быстрее!

Вот результаты R2015b, любезно предоставленные @Shaked. Это большое изменение: ООП значительно быстрее, и теперь obj.method()синтаксис такой же быстрый method(obj)и намного быстрее, чем унаследованные объекты ООП.

Matlab R2015b на PCWIN64  
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 на PCWIN64 Windows 8 6.2 (с нанизанным током) 
Машина: Core i7-4720HQ CPU @ 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 
постоянная класса: 0,25 
Свойство classdef: 0,25 
Свойство classdef с геттером: 0,64 
Функция + pkg.nop (): 0,04 
+ pkg.nop () изнутри + pkg: 0,04 
feval ('nop'): 8,26 
февал (@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 
Доступ к полю struct s.foo: 0,25 
isempty (постоянный): 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 CPU @ 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 
постоянная класса: 0,16 
Свойство classdef: 0,13 
Свойство classdef с геттером: 0,39 
Функция + pkg.nop (): 0,02 
+ pkg.nop () изнутри + pkg: 0,02 
feval ('nop'): 15,62 
февал (@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 
Доступ к полю struct 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
Эндрю Янке
2
@Seeda: Статические методы перечислены в качестве "classdef class.static_nop ()" в этих результатах. Они довольно медленные по сравнению с функциями. Если их не часто называют, это не имеет значения.
Эндрю Янке
2
@AndrewJanke Вот оно: gist.github.com/ShakedDovrat/62db9e8f6883c5e28fc0
Shaked
2
Вот Это Да! Если эти результаты подтвердятся, мне, возможно, придется пересмотреть весь этот ответ. Добавлен. Спасибо!
Эндрю Янке
3

У класса handle есть дополнительные издержки от отслеживания всех ссылок на себя в целях очистки.

Попробуйте тот же эксперимент без использования класса handle и посмотрите, каковы ваши результаты.

MikeEL
источник
1
точно такой же эксперимент со String, но теперь как класс значений (хотя и на другой машине); тест: 0.009, тест: o.356. Это в основном та же разница, что и с дескриптором, поэтому я не думаю, что отслеживание ссылок является ключевым ответом. Это также не объясняет издержки в функциях против функций в пакетах.
Стиджн
Какую версию Matlab вы используете?
MikeEL
1
Я провел несколько аналогичных сравнений между классами handle и value и не заметил различий в производительности между ними.
RjOllos
Я больше не замечаю разницу.
MikeEL
Имеет смысл: в Matlab все массивы, а не только объекты обрабатываются, подсчитываются по ссылкам, потому что они используют копирование при записи и разделяют базовые необработанные данные.
Эндрю Янке
1

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

HG Брюс
источник
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 

Поэтому для эффективной работы избегайте использования ООП, а структура - хороший выбор для группировки переменных.

Ahmad
источник