Эта статья является продолжением статей:
Простой тест libjit vs llvm
Простой тест llvm/libjit часть II, те же + gnu lightning .

Вступительный реверанс



В предыдущих статьях рссматривалась производительность llvm, libjit и gnu lightning на примере решета эратосфена. Все из рассмотренных вариантов — низкоуровневые библиотеки, которые имеют хорошую скорострельность, но по существу являются специализированными ассемблерами, и, например, работу со строками придется реализовывать самому.

Есть и другие виртуальные машины, которые можно включить в свою программу в качестве backend для своих DSL. Например — родившийся в результате первоапрельской шуткиparrot — основа будущего перла.

Там возможностей — намного больше, включая строки, хэш-таблицы, безразмерные массивы, garbage collection, консольный и файловый ввод-вывод и другие приятные вещи. Вопрос — какова цена (в потере производительности) за все эти приятные добавления?

��авайте попробуем.

Решето эратосфена на parrot


Как и прежде — это две процедуры, одна из них (erato) реализует сам алгоритм, вторая (main) запускает 100_000 раз erato для поиска простых чисел от 1 до 50_000.

.loadlib 'trans_ops'
.loadlib 'math_ops'

.sub 'erato'
.param int n
$P0 = new 'ResizableIntegerArray' # a = P0
$P0 = n
$N0 = sqrt n # q = I0
$I0 = floor $N0 # i = I1
$I1 = 2
for_cond:
if $I1 > $I0 goto for_end
$I2 = $P0[$I1]
if $I2 == 1 goto end_if
$I3 = $I1 * $I1 # j = I3
while_cond:
if $I3 > n goto while_end
$P0[$I3] = 1
$I3 += $I1
goto while_cond
while_end:
end_if:
$I1 += 1
goto for_cond
for_end:
.end

.sub main :main
$I10 = 0
for_test1:
if $I0>100000 goto for_end1
erato(50000)
$I0 += 1
goto for_test1
for_end1:
.end



И — управляющая программа, которая все это запускает:

001:  #include <parrot/embed.h>
002:  #include <parrot/extend.h>
003:  

004:  int main(int argc, char* argv[])
005:  {
006:          Parrot_Interp interp;
007:          Parrot_PackFile pf;
008:  
009:          interp = Parrot_new(NULL);
010:          if (!interp) {
011:              return 1;
012:          }
013:  
014:          pf = Parrot_pbc_read(interp, "erato.pbc"0);
015:          Parrot_pbc_load(interp, pf);
016:          Parrot_runcode(interp, argc, argv);
017:  
018:          Parrot_destroy(interp);
019:  
020:          return 0;
021:   }
022:  


Итак, компиляция нового варианта:
parrot -o erato.pbc erato.ptr
gcc -O2 erato.c -I /usr/include/parrot/2.0.0/ -lparrot -o erato

И запуск:
/usr/bin/time -f "%U" ./erato
2361.2

Продукты и плоды авторского движения мысли


Итого, наша таблица приобретает вид:

VM Время выполнения в секундах, (меньше=лучше)
LLVM 13.77
LIBJIT 14.17
GNU LIGHTNING 32.59
PARROT 2361
И просто для информации:
gcc -O0 50.09
gcc -O1 13.79
аналогичная программа на perl 4288


То есть виртуальная машина parrot работает на нашем примере в 150 раз медленнее, чем jit машины. Но в 2 раза быстрее, чем программа на перле. Это на самом деле хорошая новость, похоже следующие версии перла, на основе parrot, со��ираются работать быстрее чем сегодня… Однако parrot явно не замена других рассмотреных машин, если нужна скорость.

Заключительный аккорд



В общем parrot оставил _очень_ приятное впечатление, все работает так, как описано, подводных камней не попалось, возможностей — море, и документация хорошая. Писать на нем после llvm — доставляет удовольствие. Так что для себя я составил примерно такие рекоммендации:

  • нужна скорость — llvm или libjit, llvm предпочтительнее (лучше инфраструктура, больше иструментов)
  • стесненные условия, например мало памяти и места на диске — gnu lightning;
  • если скорость не слишком важна, но нужно удобство, работа со строками или сложные структуры — parrot