вторник, 30 июня 2009 г.

Скорость Rakudo

Как говорилось в предыдущей заметке пришло время посмотреть на Rakudo (Parrot реализация Perl6) с практической точки зрения.

Собираем свежие Parrot и Rakudo (2009-06-30) и запускаем:

> time ./perl6 -e 'say "Just another Perl Hacker"'
Just another Perl Hacker
3.180u 0.203s 0:04.19 80.6% 4979+37881k 0+10io 0pf+0w

Что-же так долго?!

Посмотрим NQP - Not Quite Perl (6):

> cd compilers/nqp
> time ../../parrot nqp.pbc -e 'say("Just another Perl Hacker")'
Just another Perl Hacker
0.518u 0.031s 0:00.70 77.1% 31+5715k 0+10io 0pf+0w

И Perl5:

> time perl -e ' print "Just another Perl Hacker\n"'
Just another Perl Hacker
0.000u 0.010s 0:00.01 100.0% 0+0k 0+0io 0pf+0w

Подробное, исследование показало, что основное время занимает загрузка самого perl6.pir. Так что, вероятно, следует направить дальнейшие поиски причины медлительности Rakudo непосредственно в сторону Parrot.

вторник, 23 июня 2009 г.

Parrot 2009 - взгляд с 2002 года

Parrot 2009 - a 2002 point of view

Недавно на своем стареньком компьютере обнаружил parrot 2002 года (версия 0.0.9)!

Да, много воды утекло. Тогда parrot ничего не знал о PASM, а PBC создавался при помощи perl:

perl assemble.pl -o foo.pbc foo.pasm
parrot foo.pbc

Конечно этот байт-код не понятен текущей версии parrot (1.3.0).

Да что говорить о PBC, если даже PASM того времени уже большей частью не совместим с современным. Например, полностью убран "классический" вариант вызова подпрограмм - следует использовать стиль передачи продолжений (Continuation-passing style), что позволило писать более компактно.

Убрали также пользовательский стек и стеки регистров, вместо них следует использовать Array PMC.

Полностью изменились Parrot Calling Conventions. Что сделало Parrot совершенно другим, а не тем, которым я знал его в 2002 году.

Текущие состояние parrot позволяет сказать:
Да здравствуют продолжения и сопрограммы!
Да здравствуют легковесные user-level потоки!
Да здравствуют возможность dataflow execution!

И самое главное, появился PIR (Parrot Intermediate Representation), за которым прячутся все тонкости работы с подпрограммами, и с которым отпадает необходимость жонглировать регистрами.

На PASM уже никто не пишет и его не развивают. Все используют PIR!

PIR - это:
+ subroutine linkage
+ named, optional, and slurpy parameters
+ method calls
+ multimethod dispatch
+ register allocation
+ macros

Имеется также Parrot Compiler Tools - набор инструментов для написание компиляторов с различных языков. Rakudo уже давно использует его. Вероятно, скоро и сам Parrot не будет нуждаться в perl.

Parrot набрал силу. Это уже не ребенок, а отрок.
Настала пора посмотреть в сторону Perl6 (Rakudo) с практической точки зрения.

среда, 17 июня 2009 г.

Поддержка YAML в Perl

Недавно узнал, что с момента моего знакомства с YAML (1.0) у него появились новые возможности. Проверим поддержку их в Perl модулях:

YAML VERSION: 0.68
YAML::XS VERSION: 0.32
YAML::Syck VERSION: 1.07

Поддержка utf-8 реализована во всех этих модулях (у YAML::Syck - включается опционально, как и многое другое). Но есть нюансы: входные данные должны быть последовательностью байтов, а не Perl строкой (подразумевается is_utf8 флаг). На выходe YAML и YAML::XS дают YAML в виде последовательности байтов, а YAML::Syck - Perl строки. YAML::Syck может принимать YAML в виде Perl строки.

Комментарии поддерживаются в YAML::XS и YAML::Syck, а в модуле YAML нет.

Ссылки на другие элементы поддерживаются всеми модулями, а слияния - ни одним из них.

Поддержка тегов и типов данных оставляет желать лучшего, так что на практике их использовать затруднительно. Модуль YAML::Syck имеет поддержку binary типа, но только для чтения.

Производительность YAML::XS и YAML::Syck примерно одинакова.

Если сравнивать YAML и JSON, то JSON в первую очередь стоит использовать для обмена информацией между программами, а YAML - для записи и редактирования информации человеком, тем более, что YAML более нагляден, чем JSON.

P.S.
Внимание! В текущей версии YAML::XS (0.32) имеется утечка памяти.

среда, 10 июня 2009 г.

Moose Benchmark - as swift as an arrow!

Moose (постмодерн объектная система для Perl 5) с первого взгляда кажется монстром. Даже в названии: Лось - животное не маленькое. Но, как говориться, у страха глаза велики, поэтому возьмем и банально измерим как он велик и быстр.

Не будем мерить скорость загрузки, компиляции... - у нас ведь persistent perl (mod_perl, FastCGI). Да и место занимаемое в памяти не так важно важно, так как мы используем операционные системы, которые умеют Copy-on-Write. Остановимся только на быстродействии.

Чтобы тест не был искусственным, возьмем объект из реального проекта. Сделаем различные реализации его и посмотрим какова скорость выполнения всех регрессионных тестов этого объекта для каждой из реализаций.

В тестировании принимали участие следующие реализации:

- классическая Perl 5;
- построенная на замыканиях - для массовки; смотрите подобный объект в заметке Объекты на замыканиях - надежная защита);
- Mouse (version 0.22) - как частичный заменитель Moose, когда есть жесткое ограничение по памяти;
- Moose (version 0.77).

Вот результаты теста (в секундах):

Perl 5 0.041
Closure 0.045
Mouse 0.074
Moose 0.105
Moose make_immutable 0.048

Моему удивлению не было предела! Работа с Mouse объектом почти в 2 раза дольше, чем с классическим, с Moose - в 2.5, Но если make_immutable, то скорость Moose близка к скорости обычных perl объектов!!!

А вот скорость Mouse подтвердила слова своего автора, что необходимо применять Moose, а не Mouse.
Mouse сделал лишь для того, чтобы можно было использовать возможности Moose там, где мало памяти и важна скорость загрузки.

Вывод. Использование Moose, при условии make_immutable, не оказывает существенного снижения производительности!

Введение в контроль версий

Предисловие

Контроль версий? А зачем он вообще нужен? Нужен, более того вы всегда используете контроль версий в той или иной степени, даже не подозревая об этом! Контроль версий - это летопись, это история. А история, как известно, - это путеводитель в будущее.

Даже, когда вы работаете в одиночку над статьей или маленькой программой, вы используете контроль версий. Вот только системой контроля версий выступает ваш ум, ваша память.

При работе над более крупным проектом без системы контроля версий уже не обойтись. Создание каждодневной копии проекта - это тоже по сути примитивная система контроля версий. Так вы страхуетесь: вы можете не бояться "поломать все", так как всегда имеете под рукой рабочую копию проекта.

При работе в команде на первый план выходит поддержка целостности системы, помогающая предотвращать ситуации, когда один разработчик "затирает" работу другого. Конечно и в этом случае можно обойтись "ручной регулировкой", но зачем, если можно автоматизировать процесс.

Экскурс в историю

У каждого произведения есть автор, а также могут быть и соавторы. Когда соавтор приносит новую версию какой либо части произведения, автору необходимо узнать об этих изменениях и внести их в оригинал.

Для этих целей была создана а начале 1970 годов программы diff. Она сравнивает два файла (оригинал и новую версию) и показывает, какие строки были изменены (diff умеет также сравнивать целые каталоги).

diff -u file_old.txt file_new.txt > file.diff

Затем автор, имея перед глазами результат работы diff, вносил правки в оригинал. Вносил, вносил, вносил... Первому надоело так делать автору программы patch, которая появилась на свет в 1985 году. Эта программа автоматически применяет патч (diff файл) к исходному файлу или каталогу:

patch < file.diff

Несмотря на солидный возраст эти программы до настоящего времени продолжают оставаться актуальными. А любой уважающий себя текстовый редактор умеет работать в diff режиме.

Вернемся к контролю версий. Хранить состояния проекта в виде полных копий слишком накладно, особенно в те далекие времена, когда объемы дисков измерялись килобайтами.
Поэтому в 1985 году используя механизмы работы diff, patch и diff3 создали RCS (Revision Control System), которая для каждого файла хранит самую последнюю версию и набор изменений между состояниями, что позволяет получить любую из предыдущих версий файла.
Однако у RCS был существенный недостаток - она не была предназначена для групповой работы, поэтому на ее основе сделали CVS (Concurrent Versions System). Но у CVS также имелись недостатки, связанные с тем, что CVS работала на уровне файлов, а не на уровне проекта целиком. В связи с этим для замены CVS была разработана более удобная в работе SVN (Subversion).

Азы "садоводства"

Поскольку репозиторий системы контроля версий, в отличии от набора копий проекта, является целостной структурой, чтобы ориентироваться в нем принято ставить метки - этакие маркеры, отмечающие различные состояний, например версии продукта.

Развитие любого продукта редко происходит линейно. Имеется главное направление, в терминах систем контроля версий - ствол, и направления развития стабильных версий продукта, а также экспериментальные направления. Направления, отличные от основного, называют ветками. То есть развитие продукта можно представить в виде абстрактного дерева.

Рассмотрим пример. Ваша команда, работая на стволе, выпустила первую версию продукта - в репозитории поставили метку RELENG_1_0_REL (RELENG - это сокращение от release engineering) и продолжили работать, двигаясь ко второй версии. В это время в релизе 1.0 обнаруживается досадная оплошность, тогда вы от метки RELENG_1_0_REL, создаете ветку RELENG_1, на которой исправляете ошибку и выпускаете версию 1.1, поставив в репозитории метку RELENG_1_1_REL. Затем оказывается, что одно реализованное на стволе важное новшество можно легко перенести в версию 1.1, так как выход версии 2.0 будет не скоро, вы принимаете решение, выпустить версию 1.2, содержащую это новшество. Для этого необходимые изменения сливаются со ствола на ветку RELENG_1, и с выпуском релиза 1.2 ставиться метка RELENG_1_2_REL.

Кроме вышеупомянутых веток, можно создавать так называемые экспериментальные ветки, на которых обкатываются какие-то уж очень неординарные новшества проекта. Если эти оказались полезными, то код с экспериментальной ветки сливают на ствол, иначе ветку "забрасывают".

Если непосредственно перед релизом требуется дополнительная подготовка, например документации или части исходников, то релиз RELENG_1_0_REL делают не на стволе, а непосредственно на ветке RELENG_1, место отщепления которой маркируют тегом RELENG_1_BP (break point). Этот тег необходим, чтобы, например, можно было посмотреть различия ветки от ствола в момент отщепления.

Конечно, есть и другие принципы построения дерева проекта, но вышеприведенный мне кажется наиболее оптимальным.

Особенности систем контроля версий

Системы контроля версий можно разбить на два типа: централизованные и распределенные. К централизованным относятся CVS, Subversion (он же SVK), к распределенным - Darcs, Mercurial, Git.

Основные особенности централизованных систем:

1. Все дерево проекта храниться в одном едином репозитории.
2. Можно получить лишь необходимую часть проекта, ветки.
3. Для работы необходимо быть online.

Основные особенности распределенных систем:

1. Каждая ветка проекта обычно - это отдельный репозиторий.
2. Нельзя получить часть проекта - только репозиторий полностью.
3. Можно работать offline.

Рассмотрим подробней каждый из 3 пунктов.

1. У централизованой системы создание ветки - это очень легкая операция, а сами ветки - всего лишь изменения. А у распеределенной системы, чтобы сделать ветку, необходимо создать полноценную копию репозитория. Чтобы минимизировать занимаемое место набор распределенных репозиториев оптимизируют за счет использования жестких ссылок файловой системы. Эту операцию желательно повторять после масштабных слияний между локальными ветками.

2. Когда вы работаете над чаcтью очень большого проекта, то возможность получить лишь необходимую составляющую - это достаточно полезное свойство централизованных систем контроля версий. Например, вас не отвлекают коммиты в те части проекта, к которым вы не имеете прямого отношения.

3. Необходимость быть online при современном развитии коммуникаций можно считать незначительным требованием. Это требование становиться еще мягче, если учесть, что можно работать не со всем репозиторием, а лишь с необходимой частью, Да и самые часто используемые команды: state и diff - можно использовать offline.

Работая с распределенной системой, не следует забывать, что чем дольше находишься offline, тем выше вероятность возникновения конфликтов. Кстати, конфликты проще решать, когда для каждого независимого участка работы используется отдельная локальная копия репозитория.

Если вы используете централизованную систему контроля версий планируете находиться вне сети продолжительное время, то можно перейти в режим создания патчей. К слову, манипулировать патчами удобно при помощи Darcs, который представляет собой скорее систему обмена патчами, нежели систему контроля версий. В крайнем случае, поверх централизованной системы всегда можно сделать распределенную, например SVK поверх SVN.

Подводя итоги, можно сказать, что выбор между централизованной и распределенной системой контроля версий целиком зависит от стиля работы команды. И централизованные, и распределенные системы имеют как преимущества так и недостатки, но ни один из них не является критичным. Совместить достоинства обоих подходов обещают в Subversion 2, добавив некоторые элементы распределенных систем.

среда, 3 июня 2009 г.

Объекты на замыканиях - надежная защита

Perl не препятствует прямому обращению и управлению данными объектов. Просто существует договоренность, что так делать не хорошо. Если все-таки необходимо явно возвести препятствие прямому доступу, можно воспользоваться Inside-out объектами. Однако Inside-out содержат лазейку, которая позволяет заглянуть за кулисы объекта (смотрите "Закрытые объекты" в Perl5 и Perl6).

Полную закрытость данных объекта обеспечивает лишь замыкание.

Рассмотрим в качестве примера объект, конструктор которого принимает число, и который имеет два метода: too - возвращает инициализирующие число, baz - возвращает результат сложения с указным числом:

package Foo;

sub new {
shift;
my $foo = shift;
my $self = \$foo;
bless $self;
return $self;
}

sub too {
my $self = shift;
return $$self;
}

sub baz {
my $self = shift;
return $$self + shift;
}

package main;
my $foo = Foo->new(2);
print $foo->too(), "\n"; # 2
print $foo->baz(3), "\n"; # 5

Замыкание с аналогичным функционалом выглядит следующим образом:

sub new_foo {
my $foo = shift;
return sub {
my $method = shift;
if ($method eq "too") {
return $foo;
} elsif ($method eq "baz") {
return $foo + shift;
}
};
}

my $foo = new_foo(2);
print $foo->("too"), "\n"; # 2
print $foo->("baz", 3), "\n"; # 5

Вызов методов объекта-замыкания несколько необычен для тех, кто привык к объектам на основе классов. К счастью, Perl очень гибкий языки и позволяет устранить вышеупомянутое "неудобство", "посвятив" быть объектом непосредственно само замыкание:

package Foo;

sub new {
shift;
my $foo = shift;
my $self = sub {
my $method = shift;
if ($method eq "too") {
return $foo;
} elsif ($method eq "baz") {
return $foo + shift;
}
};
bless $self;
return $self;
}

foreach my $method (qw(too baz)){
no strict 'refs';
*$method = sub { my $self = shift; return $self->($method, @_) };
}

package main;
my $foo = Foo->new(2);
print $foo->too(), "\n"; # 2
print $foo->baz(3), "\n"; # 5

Как видим, все просто, единственно, что пришлось добавить, так это трансляцию методов доступа в вызовы замыкания. Но при этом, имея единственную точку входа, можно легко добавить различные проверки доступа к методам объекта.