Pull to refresh

MatLab и ООП, оптимизация

Reading time3 min
Views20K
Данный пост предназначен для людей, имеющих практику программирования ООП (извините за тавтологию), волей судеб вынужденых писать на 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


Потеря производительности по сравнению со скриптом становится пренебрежимо малой.
Tags:
Hubs:
Total votes 23: ↑21 and ↓2+19
Comments22

Articles