[Moscow.pm] C3, НЕ умирающий perl, производительность и шаблоны проектирования

Vladimir V. Perepelitsa inthrax на gmail.com
Пн Дек 8 12:38:38 PST 2008


C3

2008/12/8 Ruslan Zakirov <ruz на bestpractical.com>:
> Начался треп^Wразговор про умирающий перл и это подтолкнуло меня
> заняться небольшим исследованием вместо написания комментариев.
>
> Moose клевый? ДА! Что там там клевого? РОЛИ, C3, ДЕКЛАРАТИВНОСТЬ!
> Зачем все эти вкусности? НЕ ЗНАЕМ! Вы используете? НЕТ!
>
> Тут встретился с проблемой абстракции и перегруженностью модуля
> опциональным функционалом. Подумал и понял, что C3 - решение моей
> проблемы.
>
> Задача: есть коллекция с встроенным итератором next:
>
> package MyApp::Collection;
> use base 'MyApp::Base';
>
> sub next {
>    my $self = shift;
>
>    if ( $self->{'i'} >= @{ $self->{'list'} } ) {
>        $self->{'i'} = 0;
>        return undef;
>    } else {
>        return $self->{'list'}[$self->{'i'}++];
>    }
> }
>
> Так я и написал в test.pl. От нее наследуется пяток другой
> непосредственных коллекций с различными типами объектов по которым
> ходим "кругами". Парочке моделей нужно фильтровать коллекцию при
> выдаче. Возникает дружный вопрос. А как это сделать?
>
> Первое решение - ALL_IN_ONE:
>
> package MyApp::Collection;
> use base 'MyApp::Base';
>
> sub filter { return 0 }
>
> sub next {
>    my $self = shift;
>
>    my $res;
>    do {
>        if ( $self->{'i'} >= @{ $self->{'list'} } ) {
>            $self->{'i'} = 0;
>            return undef;
>        } else {
>            $res = $self->{'list'}[$self->{'i'}++];
>        }
>    } while $self->filter( $res );
>    return $res;
> }
>
> Просто и аккуратно, но тут появляется задача не только фильтрации, но
> и каких-то действий, например что-то типа map. Это только для примера,
> но можно смыслить абстрактно. Логично делаем следующее:
>
> sub wrap { return $_[1] }
>
> sub next {
>    ...
>    return $self->wrap($res);
> }
>
> Клево, но с каждым таким изменением код все больше и сложнее. И кто
> сказал, что мы всегда хотим wrap'нуть после фильтрации? Может нужно в
> другом порядке?
>
> Переходим к подклассам и линейному наследованию. Возвращаем начальную
> реализацию коллекции и добавляем:
>
> package MyApp::Collection::WithFilter;
> use base 'MyApp::Collection';
>
> sub filter { return 0 }
>
> sub next {
>    my $self = shift;
>    my $res;
>    AGAIN:
>    $res = $self->SUPER::next(@_);
>    goto AGAIN if defined $res && $self->filter( $res );
>    return $res;
> }
>
> Где нужен фильтр, мы наследуем этот класс, получаем: Nodes isa
> Collection::WithFilter isa Collection. Как сюда добавить WithWrap? А
> никак. Во всяком случае проблему вариации последовательности фильтра и
> обертки нам не решить без игры с @ISA и/или генерации мини классов.
>
> Переходим к C3:
>
> package MyApp::Collection::WithWrapper;
> use Class::C3;
>
> sub wrap { return $_[1] }
>
> sub next {
>    my $self = shift;
>    my $res = $self->next::method(@_);
>    return undef unless defined $res;
>    return $self->wrap($res);
> }
>
> package MyApp::Nodes;
> use base 'MyApp::Collection::WithWrapper',
> 'MyApp::Collection::WithFilter', 'MyApp::Collection';
> use Class::C3;
>
> sub filter { return $_[1] % 2 }
> sub wrap { return "'$_[1]'" }
>
> И оно работает. Основное отличие $self->next::method(@_); Нужен другой
> порядок? Переставили классы местами.
>
> Зачем все это? Код класса меньше, код имеет меньше условий, легче
> следить и расширять. И нет нагрузки на объекты, где функционал не
> используется. А велика ли нагрузка? А сколько мы выиграли? Проиграли?
>
> Создаем класс без фильтра, обертки и тестируем. С реализацией "все в
> одном" он проигрывает классу, где используется фильтрация. Почему?
> Меньше вызовов метода wrap так как часть объектов отфильтрована
> простым фильтром. Класс без наворотов с C3 в ~1.7 раз быстрее чем
> all-in-one реализация.
>
> Хм, а что с C3? А в C3 класс без фильтров и оберток получает
> заслуженный прирост производительности, но класс со всеми наворотами
> получает пенальти и становиться в 3-6 раз медленнее. О УЖОСС! Кто
> сказал? Берем линейное наследование и стандартный вариант поиска
> методов и получаем схожее замедление. Разница между C3 и стандартным
> поиском методов остается только в ГИБКОСТИ решений. Хотя С3
> проигрывает all-in-one в 3.44 раза.
>
> Выводы: производительность в микро-тестах это конечно хорошо, клевые
> циферки и прочее, но мы в реальном мире, решаем различные веселые
> задачи каждый день, а для этого чаще нужны гибкие инструменты нежели
> циферки. Больше циферок мы проигрываем в других местах и алгоритмах
> более высокого уровня. Решил остановиться на all-in-one в моей задаче
> так как объекты с фильтрами используются 80% времени. Если будет
> наоборот или какая-то система с линейным наследованием потребует
> больше гибкости, то я перейду на C3.
>
> В атачах тесты которые вы можете запустить, поиграться, выкинуть и забыть :)
>
> --
> Best regards, Ruslan.
>
> --
> Moscow.pm mailing list
> moscow-pm на pm.org | http://moscow.pm.org
>
>



-- 
Best wishes,
Vladimir V. Perepelitsa aka Mons <inthrax на gmail.com>


Подробная информация о списке рассылки Moscow-pm