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 

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

Другие вопросы по тегам