Pull to refresh

Производительность пакетов ООП в Perl

Reading time 5 min
Views 5.8K
Как известно в Perl не очень удобная поддержка объектно-ориентированного программирования. Если хочется программировать с классами, то многое приходится делать вручную. Однако у Perl'а есть очень богатые возможности расширения, поэтому со временем появилось много библиотек (пакетов) обеспечивающих поддержку классов, методов и свойств с синтаксисом различной степени удобности. Но как оказалось, эти пакеты проигрывают в производительности по сравнению с ручной реализацией конструкций ООП. Т.е. с одной стороны, их приятно использовать, а с другой, они делают код медленнее. Мне всегда хотелось узнать насколько медленее становится код, и какие из этих пакетов стоит применять, а какие нет. Поэтому я решил провести небольшое исследование.


Обзор пакетов ООП

Для начала проведем небольшой обзор имеющихся пакетов ООП для Perl'а.

Просто Perl

Безо всяких пакетов класс на Perl'е можно создать так:
package Dog;

sub new {
    my ($class, %self) = @_;
    bless \%self, $class;
    return \%self;
}

sub make_noise {
    my $self = shift;
    say $self->{name}, " says: ruff-ruff!";
}

1;

После чего такой класс можно использовать следующим образом:
use Dog;

my $dog = Dog->new(name => "Snoopy");
$dog->make_noise();

Moose

В основы пакета Moose легли идеи из Perl6. С его помощью создавать классы на порядок проще. Предыдущий пример, переписанный с помощью Moose, будет выглядеть гораздо лучше:
package Dog;
use Moose;

has name => (is => 'ro', isa => 'Str');

sub make_noise {
    my $self = shift;
    say $self->name(), " says: ruff-ruff!";
}

__PACKAGE__->meta->make_immutable;

Как видно, уже не нужно явно создавать метод new, а свойства теперь описываются гораздо легче и естественнее. Но еще не все гладко. Нам приходится вручную разпаковывать параметр $self в каждом методе класса. И вообще методы остались просто перловскими процедурами. Хотелось бы, чтобы они выглядели больше похожими на нормальные методы.

Method::Signatures

И тогда нам на помощь приходит этот замечательный пакет: Method::Signatures. Советую почитать по нему документацию — там много приятных неожиданностей.

С помощью него наш код становится еще лучше:
package Dog;
use Moose;
use Method::Signatures;

has name => (is => 'ro', isa => 'Str');

method make_noise() {
    say $self->name(), " says: ruff-ruff!";
}

__PACKAGE__->meta->make_immutable;

MooseX::Declare

Пытливые умы адептов Perl'а на этом не остановились, а пошли гораздо дальше и написали MooseX::Declare. Поистине замечательный пакет, если бы не один недостаток (о котором позднее). Теперь мы можем достичь полной нирваны в описании нашего класса Dog:
use MooseX::Declare;

class Dog {
    has name => (is => 'ro', isa => 'Str');

    method make_noise() {
        say $self->name(), " says: ruff-ruff!";
    }
}

Производительность пакетов ООП

Perl позволяет достичь почти полной естественности в описании классов. Но не стоит радоваться. Оказывается эта естественность приводит к потере производительности. Чтобы выяснить насколько, сравним такие характеристики как:
  • Создание объекта класса.
  • Вызов метода.
  • Доступ к свойству.

И так приступим. Для тестирования будет использоваться пакет Benchmark. В результатах тестов обозначения следующие:
  • vanilla — чистый Perl без дополнительных пакетов,
  • moose — с пакетом Moose,
  • moose_sig — с пакетоми Moose и Method::Signatures,
  • moosex — с пакетом MooseX.

Создание объекта

              Rate moose_sig     moose    moosex   vanilla
moose_sig 104102/s        --       -0%       -0%      -74%
moose     104383/s        0%        --       -0%      -74%
moosex    104592/s        0%        0%        --      -74%
vanilla   401924/s      286%      285%      284%        --

Как видно, создание объектов с помощью всех ООП пакетов почти в 4 раза медленнее, чем на чистом Perl'е. Но есть и одина хорошая новость: операция создания объекта с использованием Moose и MooseX::Declare происходит за одинаковое время. Поэтому если использовать один из этих пакетов, то предпочтение нужно отдать MooseX::Declare.

Вызов метода

Перейдем к следующему пункту, вызову метода. Здесь картина не столь замечательна:

              Rate    moosex     moose moose_sig   vanilla
moosex      8596/s        --      -94%      -94%      -95%
moose     148055/s     1622%        --       -0%      -17%
moose_sig 148516/s     1628%        0%        --      -17%
vanilla   178254/s     1974%       20%       20%        --

MooseX проигрывает Moose (с Signatures или без) в 16 раз! Это конечно большой сюрприз. Если порыться в интернете, то можно обнаружить, что причина такой большой разницы в производительности, пакет MooseX::Method::Signatures. Поэтому, пока ситуация не исправится в лучшую сторону, MooseX, такой какой он есть, использовать нельзя.

Однако, хорошая новость заключается в том, что Moose + Method::Signatures медленнее чистого Perl'а всего на 20%. Поэтому можно смело использовать эти пакеты без какой либо существенной потери производительности.

Доступ к свойству

Перейдем к последнему пункту тестов, доступ к свойству объекта:

               Rate    moosex moose_sig     moose   vanilla
moosex    1503608/s        --       -1%       -1%      -17%
moose_sig 1517928/s        1%        --       -0%      -16%
moose     1517928/s        1%        0%        --      -16%
vanilla   1815063/s       21%       20%       20%        --

Опять, проигрыш всех пакетов по сравнению с чистым Perl'ом только 20%. Как видно, здесь MooseX оказался не хуже остальных пакетов. Если бы только не MooseX::Method::Signatures…

Исправляем MooseX

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

Первое, что приходит в голову, использовать Method::Signatures вместе с MooseX::Declare. Перепишем код Dog следующим образом:
use MooseX::Declare;

class Dog {
    use Method::Signatures;

    has name => (is => 'ro', isa => 'Str');

    method make_noise() {
        say $self->name(), " says: ruff-ruff!";
    }
}

Этот подход работает, но приводит к некрасивому предупреждению:

Subroutine Dog::method redefined at /opt/local/lib/perl5/site_perl/5.14.1/darwin-thread-multi-2level/Devel/Declare/MethodInstaller/Simple.pm line 17.
Prototype mismatch: sub Dog::method: none vs (&) at /opt/local/lib/perl5/site_perl/5.14.1/darwin-thread-multi-2level/Devel/Declare/MethodInstaller/Simple.pm line 17.


Дело в том, что method является простой функцией Perl'а, которую определяют как MooseX::Method::Signatures, так и Method::Signatures. Но определяют с разными прототипами, откуда и возникает предупреждение. К сожалению, я не нашел, как это предупреждение убрать. Поэтому пришлось придумать другое решение.

Method::Signatures::Simple

Что может быть проще! Method::Signatures::Simple — это упрощенная версия Method::Signatures, которая обладает одним хорошим свойством. В ней можно задать свое имя для функции method, таким образом избежав конфликта с MooseX::Method::Signatures.

С использованием этого пакета Dog будет выглядеть так:
use MooseX::Declare;

class Dog {
    use Method::Signatures::Simple name => 'def';

    has name => (is => 'ro', isa => 'Str');

    def make_noise() {
        say $self->name(), " says: ruff-ruff!";
    }
}

Разница в том, что вместо method мы теперь пишем def (как в Python).

Посмотрим, улучшилась ли производительность нашего кода (moosex_sig):

               Rate     moosex moosex_sig  moose_sig      moose    vanilla
moosex       8651/s         --       -94%       -94%       -94%       -95%
moosex_sig 146152/s      1589%         --        -1%        -3%       -18%
moose_sig  148025/s      1611%         1%         --        -2%       -17%
moose      150866/s      1644%         3%         2%         --       -16%
vanilla    179200/s      1971%        23%        21%        19%         --

Наши ожидания оправдались. MooseX::Declare + Method::Signature::Simple дает такую же производительность как и Moose + Method::Signatures.

Вывод

Несмотря на все богатство выбора, альтернатива вырисовывается всего одна. На сегодняшний день лучшей с моей точки зрения рекомендацией для использования ООП в Perl является комбинация пакетов MooseX::Declare и Method::Signatures::Simple. Она позволяет описывать классы естественным образом и имеет не очень большую, можно даже сказать, вполне приемлимую цену по производительности.

Добавление: исходный код для тестов доступен на github: github.com/alexeiz/oopbench
Tags:
Hubs:
+37
Comments 62
Comments Comments 62

Articles