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

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


fucking gmail...
хотел сказать:
C3 удобный паттерн.
имхо более адекватный чем классический метод

2008/12/8 Vladimir V. Perepelitsa <inthrax на gmail.com>:
> 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 wishes,
Vladimir V. Perepelitsa aka Mons <inthrax на gmail.com>


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