Данный пост предназначен для людей, имеющих практику программирования ООП (извините за тавтологию), волей судеб вынужденых писать на MatLab. Язык приятный, но граблей достаточно большое количество, и не обязательно каждому наступить на них все.
На написание вдохновил недавний пост об оптимизации кода на MatLab, точнее комментарии, требовавшие большего углубления в тему.
Итак…
В MatLab ООП долгое время вообще не было.
Году в 2005 появились первые, страшно выглядящие потуги: класс — папка, метод — отдельный файл, свойства — единый метод доступа с параметром «имя свойства».
«Прекрасное» начало, к счастью от него быстро отказались.
Ещё через несколько лет сделали ООП в более привычном виде — классы, наследование, всё как у людей. Кроме нескольких деталей.
До сих пор невозможно определить несколько методов с одним именем. Сам MatLab объясняет это отсутствием типизации переменных (единственное отличие разных методов было бы количество параметров) и необязательность передачи параметров в функции (приехали).
Выход — писать множество функций с похожими именами, возможно вместе с функцией-диспетчером, анализирующую ситуацию и вызывающую одну из этих функций.
Невозможно определить abstract static методы или свойства. А иногда очень хочется.
Выхода пока не нашёл, MatLab в курсе, багом не считает.
Все параметры передаются по значению (что заставляет многих пользователей ратовать за отход от использования функций — ужас, ужас!), но через lazy method. То есть копирование происходит только в случае изменения переменной. Если этот механизм хорошо понимать, можно выиграть (точнее не потерять по сравнению с привычной передачей переменных по ссылке) в скорости вычислений.
Объекты «обычных» классов также передаются по значению.
Впрочем, если класс наследует от стандартного MatLab класса handle, его объекты станут передаваться по ссылке.
После java/C++ привыкаешь, что документация должна автоматически создаваться на основе оставленных тобою в коде комментариев. MatLab предлагает мутанта под названием Publisher, который шатко-валко справляется с задачей на уровне скриптов. Помимо того, что он не работает с классами, замечу факт включения в документацию всех комментариев, а не только тех, которые я хотел бы. Что означает как отказ от комментариев для себя («А вот эту функцию срочно переписать!»), а также от очень удобной системы навигации в коде по cells, которая тоже почему-то завязана на синтаксисе комментариев.
К счастью умельцы прикрутили perl-script, конвертирующий MatLab-классы в C++ (только декларирование и комментарии, естественно), что позволяет использовать «стандартный» DOxygen со всеми его прелестями — внутренние ссылки, изображения, LaTeX и пр.
Проект (я к нему отношения не имею, но рекламирую) доступен на MatLab Central.
При переходе от обычного скрипта/функции к классу я однажды обнаружил падение производительности в 40 раз. Простое copy-paste с парочкой причёсываний, чтобы было похоже на класс — и всё, код становится в 40 раз медленнее.
Покопавшись, обнаружил, что MatLab тормозит во время доступа к переменным класса из методов того же класса. Возьмём следующий класс, единственный метод которого в разы медленнее аналогичной функции:
MatLab считает это «разумным overhead» при переходе на ООП. То есть на каждой итерации они проверяют, не изменилась ли переменная каким-то сторонним процессов, имеем ли мы до сих пор доступ к ней (как будто кто-то реально может написать такой morphe-код на MatLab) и т.п. «полезные» операции.
После долгого разговора с несколькими вменяемыми людьми MatLab вроде как согласился подумать над более приличной реализацией.
В ожидании чуда выход — определять на входе в метод локальные переменные:
Потеря производительности по сравнению со скриптом становится пренебрежимо малой.
На написание вдохновил недавний пост об оптимизации кода на MatLab, точнее комментарии, требовавшие большего углубления в тему.
Итак…
История
В MatLab ООП долгое время вообще не было.
Году в 2005 появились первые, страшно выглядящие потуги: класс — папка, метод — отдельный файл, свойства — единый метод доступа с параметром «имя свойства».
«Прекрасное» начало, к счастью от него быстро отказались.
Ещё через несколько лет сделали ООП в более привычном виде — классы, наследование, всё как у людей. Кроме нескольких деталей.
Несколько деталей
Overload
До сих пор невозможно определить несколько методов с одним именем. Сам MatLab объясняет это отсутствием типизации переменных (единственное отличие разных методов было бы количество параметров) и необязательность передачи параметров в функции (приехали).
Выход — писать множество функций с похожими именами, возможно вместе с функцией-диспетчером, анализирующую ситуацию и вызывающую одну из этих функций.
Abstract static
Невозможно определить abstract static методы или свойства. А иногда очень хочется.
Выхода пока не нашёл, MatLab в курсе, багом не считает.
Передача параметров по значению или по ссылке
Все параметры передаются по значению (что заставляет многих пользователей ратовать за отход от использования функций — ужас, ужас!), но через lazy method. То есть копирование происходит только в случае изменения переменной. Если этот механизм хорошо понимать, можно выиграть (точнее не потерять по сравнению с привычной передачей переменных по ссылке) в скорости вычислений.
Объекты «обычных» классов также передаются по значению.
Впрочем, если класс наследует от стандартного MatLab класса handle, его объекты станут передаваться по ссылке.
Генерируемая документация
После java/C++ привыкаешь, что документация должна автоматически создаваться на основе оставленных тобою в коде комментариев. MatLab предлагает мутанта под названием Publisher, который шатко-валко справляется с задачей на уровне скриптов. Помимо того, что он не работает с классами, замечу факт включения в документацию всех комментариев, а не только тех, которые я хотел бы. Что означает как отказ от комментариев для себя («А вот эту функцию срочно переписать!»), а также от очень удобной системы навигации в коде по cells, которая тоже почему-то завязана на синтаксисе комментариев.
К счастью умельцы прикрутили perl-script, конвертирующий MatLab-классы в C++ (только декларирование и комментарии, естественно), что позволяет использовать «стандартный» DOxygen со всеми его прелестями — внутренние ссылки, изображения, LaTeX и пр.
Проект (я к нему отношения не имею, но рекламирую) доступен на MatLab Central.
Скорость в ООП
При переходе от обычного скрипта/функции к классу я однажды обнаружил падение производительности в 40 раз. Простое copy-paste с парочкой причёсываний, чтобы было похоже на класс — и всё, код становится в 40 раз медленнее.
Покопавшись, обнаружил, что MatLab тормозит во время доступа к переменным класса из методов того же класса. Возьмём следующий класс, единственный метод которого в разы медленнее аналогичной функции:
classdef Toto < handle
properties
RatingDepart
TauxRecouv
Weight
FluxScenario
end
methods
function obj = CollectionTaux2()
end
function corrigerFluxScenario(obj, Survie, CoefRedGov, CoefRedCredit)
nbScenario = size(obj.FluxScenario, 3);
for i = 1 : nbScenario
for j = 1 : numel(obj.Weight)
obj.FluxScenario(i, j, :) = repmat(obj.Weight(j), 1, nbScenario) .* ...
reshape(obj.FluxScenario(i, j, :), 1, nbScenario) .* ...
(CoefRedGov' .* obj.TauxRecouv(j) + ...
CoefRedCredit(:, obj.RatingDepart(j))' .* ...
Survie(i, :, obj.RatingDepart(j)) .* (1 - obj.TauxRecouv(j)));
end
end
end
end
end
MatLab считает это «разумным overhead» при переходе на ООП. То есть на каждой итерации они проверяют, не изменилась ли переменная каким-то сторонним процессов, имеем ли мы до сих пор доступ к ней (как будто кто-то реально может написать такой morphe-код на MatLab) и т.п. «полезные» операции.
После долгого разговора с несколькими вменяемыми людьми MatLab вроде как согласился подумать над более приличной реализацией.
В ожидании чуда выход — определять на входе в метод локальные переменные:
function corrigerFluxScenario(obj, Survie, CoefRedGov, CoefRedCredit)
localFluxScenario = obj.FluxScenario;
localWeight = obj.Weight;
localRatingDepart = obj.RatingDepart;
localTauxRecouv = obj.TauxRecouv;
nbScenario = size(localFluxScenario, 3);
for i = 1 : nbScenario
for j = 1 : numel(localWeight)
localFluxScenario(i, j, :) = repmat(localWeight(j), 1, nbScenario) .* ...
reshape(localFluxScenario(i, j, :), 1, nbScenario) .* ...
(CoefRedGov' .* localTauxRecouv(j) + ...
CoefRedCredit(:, localRatingDepart(j))' .* ...
Survie(i, :, localRatingDepart(j)) .* (1 - localTauxRecouv(j)));
end
end
obj.FluxScenario = localFluxScenario;
end
Потеря производительности по сравнению со скриптом становится пренебрежимо малой.