Я экспериментирую с MATLAB OOP, в качестве начала я подражал моим классам С++ Logger, и я помещаю все свои вспомогательные функции строки в класс String, полагая, что было бы здорово сделать такие вещи, как a + b
, a == b
, a.find( b )
из strcat( a b )
, strcmp( a, b )
, получить первый элемент strfind( a, b )
и т.д.
Проблема: замедление
Я поставил вышеприведенные вещи и сразу заметил резкое замедление. Я делаю это неправильно (что, безусловно, возможно, поскольку у меня довольно ограниченный опыт MATLAB), или MATLAB OOP просто представляет много накладных расходов?
Мой тестовый пример
Вот простой тест, который я сделал для строки, в основном просто добавляя строку и снова удаляя добавленную часть:
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 (ОК, в нем есть намного больше логики, но для сравнения с С++: накладные расходы моей системы, которая использует std::string( "blah" )
и std::cout
на выходной стороне vs plain std::cout << "blah"
составляет порядка 1 миллисекунды.)
Это просто накладные расходы при поиске функций класса/пакета?
Поскольку MATLAB интерпретируется, он должен искать определение функции/объекта во время выполнения. Поэтому мне было интересно, что, возможно, гораздо больше накладных расходов связано с поиском функций класса или пакета с функциями, находящимися на пути. Я попытался проверить это, и он просто становится незнакомцем. Чтобы исключить влияние классов/объектов, я сравнил вызов функции в пути vs функции в пакете:
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, тогда как эти служебные данные не существуют для функций, которые находятся непосредственно на пути?
Я работаю с OO MATLAB некоторое время и в конечном итоге смотрю на аналогичные проблемы с производительностью.
Короткий ответ: да, MATLAB OOP медленный. Существует существенная накладная стоимость вызова метода, выше, чем обычные языки OO, и вы не можете с этим справиться. Частью причины может быть то, что идиоматический 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 x64 с 32-разрядным MATLAB.
"Java nop()" - это Java-метод do-nothing, вызываемый из цикла M-кода, и включает в себя накладные расходы MATLAB-to-Java при каждом вызове. "Java nop() из Java" - это то же самое, что и в цикле Java for() и не несет этого штрафа за границы. Возьмите таймеры Java и C с солью; умный компилятор может полностью оптимизировать вызовы.
Механизм определения пакета является новым, введенным примерно в то же время, что и классы classdef. Его поведение может быть связано.
Несколько предварительных заключений:
obj.nop()
медленнее, чем синтаксис nop(obj)
, даже для того же метода для объекта classdef. То же самое для объектов Java (не показано). Если вы хотите быстро, вызовите nop(obj)
.Сказать, почему это так, было бы просто спекуляцией с моей стороны. Механизм OAT MATLAB не является общедоступным. Это не интерпретированная скомпилированная проблема как таковая - MATLAB имеет JIT, но MATLAB более легкая типизация и синтаксис могут означать большую работу во время выполнения. (Например, вы не можете сказать только из синтаксиса: "f (x)" - это вызов функции или индекс в массив, это зависит от состояния рабочей области во время выполнения.) Возможно, это связано с тем, что определения класса MATLAB связаны к состоянию файловой системы таким образом, что многие другие языки не являются.
Итак, что делать?
Идиоматический подход MATLAB к этому заключается в "векторизации" вашего кода путем структурирования ваших определений классов, так что экземпляр объекта обертывает массив; то есть каждое из его полей содержит параллельные массивы (называемые "планарной" организацией в документации MATLAB). Вместо того, чтобы иметь массив объектов, каждый с полями, содержащими скалярные значения, определяет объекты, которые являются самими массивами, и методы принимают массивы в качестве входов и делают векторизованные вызовы для полей и входов. Это уменьшает количество сделанных вызовов методов, мы надеемся, что накладные расходы на отправку не являются узким местом.
Воспроизведение класса С++ или Java в MATLAB, вероятно, не будет оптимальным. Классы Java/С++ обычно построены таким образом, что объекты являются наименьшими строительными блоками, такими конкретными, как вы можете (то есть, множеством разных классов), и вы создаете их в массивах, объектах коллекции и т.д. И перебираете их с помощью циклов. Чтобы сделать быстрые классы MATLAB, сделайте этот подход наизнанку. Имеют более крупные классы, поля которых являются массивами, и вызывают векторизованные методы на этих массивах.
Цель состоит в том, чтобы расположить свой код для сильных сторон языка - обработку массива, векторизованную математику - и избежать слабых мест.
РЕДАКТИРОВАНИЕ: после того, как вышли оригинальные сообщения, R2010b и R2011a. Общая картина такая же, что и вызовы MCOS становятся немного быстрее, а методы Java и метода старого стиля становятся медленнее.
EDIT: у меня были некоторые примечания здесь о "чувствительности пути" с дополнительной таблицей таймингов вызовов функций, где время функции зависело от того, как был настроен путь Matlab, но это, по-видимому, было аберрацией моего конкретного настройки сети в то время. В приведенной выше таблице отражены времена, типичные для преобладания моих тестов с течением времени.
РЕДАКТИРОВАТЬ (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
Я думаю, что результатом этого является то, что:
foo(obj)
. Таким образом, скорость метода больше не является основанием для использования старых классов стиля в большинстве случаев. (Kudos, MathWorks!)Я восстановил код бенчмаркинга и запустил его на 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, любезно предоставленные @Shaked. Это большое изменение: OOP значительно быстрее, и теперь синтаксис 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
Я поставил исходный код этих тестов на GitHub, выпущенный под лицензией MIT. https://github.com/apjanke/matlab-bench
Класс handle имеет дополнительные накладные расходы от отслеживания всех ссылок на себя для целей очистки.
Попробуйте тот же эксперимент, не используя класс дескриптора, и посмотрите, какие у вас результаты.
На самом деле никаких проблем с вашим кодом, но это проблема с 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, а структура - хороший выбор для группировки переменных
Производительность OO существенно зависит от используемой версии MATLAB. Я не могу комментировать все версии, но по опыту знаю, что 2012a значительно улучшилось в версиях 2010 года. Нет контрольных показателей, и поэтому нет цифр для представления. Мой код, написанный исключительно с использованием классов дескрипторов и написанный под 2012a, не будет запускаться вообще в более ранних версиях.