Является ли MATLAB ООП медленным или я делаю что-то неправильно?
я экспериментирую с MATLABОП, для начала я имитировал классы логгера моего C++, и я помещаю все свои вспомогательные функции string в класс String, думая, что было бы здорово иметь возможность делать такие вещи, как a + b,a == b,
из strcat( a b ),strcmp( a, b ) извлечь первый элемент strfind( a, b ) и т. д.
проблема: замедление
я поставил выше вещи, чтобы использовать и сразу же заметил кардинальные замедление. Я делаю это неправильно (что, безусловно, возможно, поскольку у меня довольно ограниченный опыт MATLAB), или ООП 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 (со строкой.SetLength 0.138, String.плюс 0.065, строку.Длина 0.057)
atest 0.015
результаты для системы регистратора аналогичны: 0,1 секунды для 1000 вызовов
к frpintf( 1, 'testn' ), 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, тратя время на поиск определений для его реализации ООП, тогда как эти накладные расходы не существуют для функций, которые находятся непосредственно в пути?
4 ответов:
Я некоторое время работал с OO MATLAB и в конечном итоге посмотрел на аналогичные проблемы с производительностью.
короткий ответ: да, ООП MATLAB немного медленный. Существует значительная накладная стоимость вызова метода, выше, чем основные языки OO, и вы не можете с этим ничего поделать. Частично причина может заключаться в том, что idiomatic MATLAB использует "векторизованный" код для уменьшения количества вызовов методов, а накладные расходы на вызов не являются высоким приоритетом.
Я сравнил производительность при написании do-nothing" nop " функции, как различные типы функций и методов. Вот некоторые типичные результаты.
>> call_nops Computer: PCWIN Release: 2009b Calling each function/method 100000 times nop() function: 0.02261 sec 0.23 usec per call nop1-5() functions: 0.02182 sec 0.22 usec per call nop() subfunction: 0.02244 sec 0.22 usec per call @()[] anonymous function: 0.08461 sec 0.85 usec per call nop(obj) method: 0.24664 sec 2.47 usec per call nop1-5(obj) methods: 0.23469 sec 2.35 usec per call nop() private function: 0.02197 sec 0.22 usec per call classdef nop(obj): 0.90547 sec 9.05 usec per call classdef obj.nop(): 1.75522 sec 17.55 usec per call classdef private_nop(obj): 0.84738 sec 8.47 usec per call classdef nop(obj) (m-file): 0.90560 sec 9.06 usec per call classdef class.staticnop(): 1.16361 sec 11.64 usec per call Java nop(): 2.43035 sec 24.30 usec per call Java static_nop(): 0.87682 sec 8.77 usec per call Java nop() from Java: 0.00014 sec 0.00 usec per call MEX mexnop(): 0.11409 sec 1.14 usec per call C nop(): 0.00001 sec 0.00 usec per callпохожие результаты на R2008a через R2009b. Это на Windows XP 64-разрядная 32-разрядная версия Матлаб.
"Java nop ()" -это метод Java do-nothing, вызываемый из цикла M-кода, и включает в себя накладные расходы MATLAB-to-Java dispatch с каждым вызовом. "Java nop () from Java" - это то же самое, что называется в цикле Java for() и не делает понесите этот пограничный штраф. Возьмите Java и C тайминги с зерном соли; умный компилятор может полностью оптимизировать вызовы.
механизм определения области пакета является новым, введенным примерно в то же время, что и классы classdef. Его поведение может быть связано.
несколько предварительных выводов:
- методы медленнее, чем функции.
- новый стиль (classdef) методы медленнее, чем старый стиль методы.
- новая
obj.nop()синтаксис медленнее, чемnop(obj)синтаксис, даже для одного и того же метода на объекте classdef. То же самое для объектов Java (не показано). Если вы хотите ехать быстро, звонитеnop(obj).- накладные расходы на вызов метода выше (около 2x) в 64-битном MATLAB на Windows. (Не показанный.)
- MATLAB метод отправки медленнее, чем некоторые другие языки.
говорить, почему это так, было бы просто спекуляцией с моей стороны. Двигатель Матлаб это ОО внутренние органы не являются публичными. Это не интерпретируемая vs скомпилированная проблема как таковая-MATLAB имеет JIT-но более свободный тип и синтаксис MATLAB могут означать больше работы во время выполнения. (Например, вы не можете сказать из одного синтаксиса, является ли "f(x)" вызовом функции или индексом в массив; это зависит от состояния рабочей области во время выполнения.) Это может быть связано с тем, что определения классов MATLAB привязаны к состоянию файловой системы таким образом, что многие другие языки не являются.
Итак, что делать?
An идиоматический подход MATLAB к этому заключается в" векторизации "вашего кода путем структурирования определений классов таким образом, что экземпляр объекта обертывает массив; то есть каждое из его полей содержит параллельные массивы (называемые" планарной " организацией в документации MATLAB). Вместо того, чтобы иметь массив объектов, каждый с полями, содержащими скалярные значения, определяют объекты, которые сами являются массивами, и имеют методы, которые принимают массивы в качестве входных данных и выполняют векторизованные вызовы полей и входных данных. Это уменьшает количество из сделанных вызовов методов, надеюсь, достаточно, чтобы накладные расходы на отправку не были узким местом.
имитация класса C++ или Java в MATLAB, вероятно, не будет оптимальной. Классы Java/C++ обычно строятся таким образом, что объекты являются наименьшими строительными блоками, насколько это возможно (то есть много разных классов), и вы составляете их в массивах, объектах коллекции и т. д. и перебираете их с циклами. Чтобы сделать быстрые классы MATLAB, поверните этот подход наизнанку. Есть большие классы чьи поля являются массивами и вызывают векторизованные методы на этих массивах.
дело в том, чтобы организовать ваш код, чтобы играть в сильные стороны языка-обработка массива, векторизованная математика-и избегать слабых мест.
EDIT: начиная с исходного сообщения, R2010b и R2011a вышли. Общая картина такая же, с вызовами MCOS становится немного быстрее, а вызовы Java и старого стиля метода получают медленнее.
EDIT: раньше у меня были некоторые Примечания здесь о "чувствительности пути" с дополнительной таблицей таймингов вызова функций, где время функции зависело от того, как был настроен путь Matlab, но это, по-видимому, было аберрацией моей конкретной настройки сети в то время. Приведенная выше диаграмма отражает время, типичное для преобладания моих тестов с течением времени.
Обновление: R2011b
EDIT (2/13/2012): R2011b вышел, и изображение производительности изменилось достаточно, чтобы обновить этот.
Arch: PCWIN Release: 2011b Machine: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB RAM, NVIDIA NVS 300 Doing each operation 100000 times style total µsec per call nop() function: 0.01578 0.16 nop(), 10x loop unroll: 0.01477 0.15 nop(), 100x loop unroll: 0.01518 0.15 nop() subfunction: 0.01559 0.16 @()[] anonymous function: 0.06400 0.64 nop(obj) method: 0.28482 2.85 nop() private function: 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 constant: 1.51890 15.19 classdef property: 0.12992 1.30 classdef property with getter: 1.39912 13.99 +pkg.nop() function: 0.87345 8.73 +pkg.nop() from inside +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() from Java: 0.00010 0.00 MEX mexnop(): 0.08709 0.87 C nop(): 0.00001 0.00 j() (builtin): 0.00251 0.03Я думаю, что результат этого таков:
- MCOS/classdef методы быстрее. Стоимость теперь примерно наравне с классами старого стиля, Если вы используете
foo(obj)синтаксис. Таким образом, скорость метода больше не является причиной придерживаться старых классов стиля в большинстве случаев. (Спасибо, Еда!)- размещение функций в пространствах имен делает их медленными. (Не новый в R2011b, просто новый в моем тесте.)
обновление: R2014a
я восстановил код бенчмаркинга и запустил его на R2014a.
Matlab R2014a on PCWIN64 Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 on PCWIN64 Windows 7 6.1 (eilonwy-win7) Machine: Core i7-3615QM CPU @ 2.30GHz, 4 GB RAM (VMware Virtual Platform) nIters = 100000 Operation Time (µsec) nop() function: 0.14 nop() subfunction: 0.14 @()[] anonymous function: 0.69 nop(obj) method: 3.28 nop() private fcn on @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 constant: 4.18 classdef property: 1.18 classdef property with getter: 19.26 +pkg.nop() function: 4.03 +pkg.nop() from inside +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() from Java: 0.01 MEX mexnop(): 0.91 builtin j(): 0.02 struct s.foo field access: 0.14 isempty(persistent): 0.00обновление: R2015b: объекты стали быстрее!
вот результаты R2015b, любезно предоставленные @Shaked. Это же большой изменение: ООП значительно быстрее, и теперь
obj.method()синтаксис так же быстро, какmethod(obj), и гораздо быстрее, чем устаревшие объекты ООП.Matlab R2015b on PCWIN64 Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 on PCWIN64 Windows 8 6.2 (nanit-shaked) Machine: Core i7-4720HQ CPU @ 2.60GHz, 16 GB RAM (20378) nIters = 100000 Operation Time (µsec) nop() function: 0.04 nop() subfunction: 0.08 @()[] anonymous function: 1.83 nop(obj) method: 3.15 nop() private fcn on @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 constant: 0.25 classdef property: 0.25 classdef property with getter: 0.64 +pkg.nop() function: 0.04 +pkg.nop() from inside +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() from Java: 0.01 MEX mexnop(): 0.33 builtin j(): 0.15 struct s.foo field access: 0.25 isempty(persistent): 0.13Обновление: R2018a
вот результаты R2018a. Это не огромный скачок это мы видели, когда новый движок исполнения был представлен в R2015b, но это все еще заметное улучшение из года в год. Примечательно, что анонимные дескрипторы функций стали намного быстрее.
Matlab R2018a on MACI64 Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 on MACI64 Mac OS X 10.13.5 (eilonwy) Machine: Core i7-3615QM CPU @ 2.30GHz, 16 GB RAM nIters = 100000 Operation Time (µsec) nop() function: 0.03 nop() subfunction: 0.04 @()[] anonymous function: 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 constant: 0.16 classdef property: 0.13 classdef property with getter: 0.39 +pkg.nop() function: 0.02 +pkg.nop() from inside +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() from Java: 0.03 MEX mexnop(): 3.54 builtin j(): 0.10 struct s.foo field access: 0.16 isempty(persistent): 0.07исходный код для тестирования
Я поставил исходный код для этих тестов на GitHub, выпущенный под лицензией MIT. https://github.com/apjanke/matlab-bench
класс дескриптора имеет дополнительные накладные расходы от отслеживания всех ссылок на себя для целей очистки.
попробуйте тот же эксперимент без использования класса handle и посмотрите, каковы ваши результаты.
производительность OO значительно зависит от используемой версии 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 % vectort4 =
0.0707 % structureпоэтому для эффективной работы избегайте использования ООП вместо структуры-это хороший выбор для группировки переменных
Comments