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

110

Я экспериментирую с 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, тогда как эти служебные данные не существуют для функций, которые находятся непосредственно на пути?

  • 5
    Спасибо за этот вопрос! Производительность кучи Matlab (ООП / замыкания) беспокоила меня годами, см. Stackoverflow.com/questions/1446281/matlabs-garbage-collector . Мне действительно любопытно, что MatlabDoug / Loren / MikeKatz ответит на ваш пост.
  • 1
    ^ это было интересное чтение.
Показать ещё 5 комментариев
Теги:
oop
benchmarking
profiling
matlab-class

4 ответа

176
Лучший ответ

Я работаю с 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. Его поведение может быть связано.

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

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

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

Итак, что делать?

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

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

Цель состоит в том, чтобы расположить свой код для сильных сторон языка - обработку массива, векторизованную математику - и избежать слабых мест.

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

EDIT: у меня были некоторые примечания здесь о "чувствительности пути" с дополнительной таблицей таймингов вызовов функций, где время функции зависело от того, как был настроен путь Matlab, но это, по-видимому, было аберрацией моего конкретного настройки сети в то время. В приведенной выше таблице отражены времена, типичные для преобладания моих тестов с течением времени.

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

РЕДАКТИРОВАТЬ (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). Таким образом, скорость метода больше не является основанием для использования старых классов стиля в большинстве случаев. (Kudos, MathWorks!)
  • Помещение функций в пространства имён делает их медленными. (Не новый в 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. Это большое изменение: 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

  • 7
    спасибо за ваш подробный ответ!
  • 5
    @AndrewJanke Как вы думаете, вы могли бы снова запустить тест с R2012a? Это действительно интересно.
Показать ещё 11 комментариев
3

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

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

  • 1
    точно такой же эксперимент со String, но теперь в качестве класса значений (хотя и на другом компьютере); тест: 0.009, тест: o.356. Это в основном та же разница, что и с дескриптором, поэтому я не думаю, что отслеживание ссылок является ключевым ответом. Это также не объясняет издержки в функциях против функций в пакетах.
  • 0
    Какую версию Matlab вы используете?
Показать ещё 3 комментария
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, а структура - хороший выбор для группировки переменных

1

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню