MATLAB OOP работает медленно или я делаю что-то не так?
Я экспериментирую с MATLAB OOP, для начала я подражал своим классам Logger в C++ и помещаю все свои вспомогательные функции для строк в класс String, думая, что было бы здорово иметь возможность делать такие вещи, как a + b
, a == b
, a.find( b )
вместо strcat( a b )
, strcmp( a, b )
, получить первый элемент strfind( a, b )
, так далее.
Проблема: замедление
Я использовал вышеперечисленные вещи и сразу заметил резкое замедление. Я делаю это неправильно (что, безусловно, возможно, поскольку у меня достаточно ограниченный опыт работы с MATLAB), или ООП MATLAB просто вносит много накладных расходов?
Мой тест
Вот простой тест, который я сделал для строки, просто добавив строку и снова удалив добавленную часть:
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 с в утилитарном тесте
Итак, все ли это связано с тем, что MATLAB тратит время на поиск определений для своей реализации ООП, тогда как эти издержки отсутствуют для функций, которые находятся непосредственно в пути?
4 ответа
Я работал с 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 с 2, 47 мкс на вызов 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 @ 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 с геттером: 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 Доступ к полю 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
Исходный код для тестов
Я поместил исходный код для этих тестов в GitHub, выпущенный под лицензией MIT. https://github.com/apjanke/matlab-bench
У класса handle есть дополнительные издержки от отслеживания всех ссылок на себя в целях очистки.
Попробуйте тот же эксперимент, не используя класс handle, и посмотрите, каковы ваши результаты.
Производительность ОО существенно зависит от используемой версии MATLAB. Я не могу комментировать все версии, но по опыту знаю, что версия 2012a значительно улучшена по сравнению с версиями 2010 года. Нет эталонов и поэтому нет цифр, чтобы представить. Мой код, написанный исключительно с использованием классов дескрипторов и написанный под 2012a, вообще не будет работать в более ранних версиях.
На самом деле нет проблем с вашим кодом, но это проблема с 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
Поэтому для эффективной работы избегайте использования ООП, а структура - хороший выбор для группировки переменных.