Hlavní navigace

RPython: překvapivě výkonný dialekt Pythonu, na němž je založen PyPy

Pavel Tišnovský

S alternativní implementací Pythonu nazvanou PyPy se již pravděpodobně přímo či nepřímo setkal každý vývojář, který tento jazyk používá. Projekt PyPy je mj. založen na transpřekladači RPythonu, který si představíme.

Doba čtení: 26 minut

Obsah

1. RPython: překvapivě výkonný dialekt Pythonu, na němž je založen PyPy

2. Použití RPythonu

3. Instalace nástroje RPython

4. První demonstrační příklad: „Hello world!“ připravený pro RPython

5. Překlad demonstračního příkladu do nativního kódu

6. Výsledek překladu

7. Ukončení překladu ve funkci target

8. Volba jiné funkce použité v runtime pro vstup do programu

9. Zpracování argumentů předaných z příkazového řádku v runtime

10. Analýza bajtkódu a „líný“ překlad

11. Základní omezení kladená RPythonem

12. Chování globálních proměnných

13. Rychlost výsledného nativního kódu produkovaného RPythonem

14. Příprava jednoduchého mikrobenchmarku orientovaného na numerické výpočty

15. Úprava kódu takovým způsobem, aby byl kompatibilní s RPythonem

16. Skripty pro spuštění benchmarku se zvoleným interpretrem a parametry

17. Malá odbočka – implementace totožného algoritmu v ANSI C

18. Výsledky benchmarků – porovnání s Pythonem 2, Pythonem 3, Jythonem a ANSI C

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. RPython: překvapivě výkonný dialekt Pythonu, na němž je založen PyPy

„RPython is half-Python, half-not-Python, and Python“

S projektem PyPy se již s velkou pravděpodobností setkalo mnoho programátorů používajících pro vývoj programovací jazyk Python. Jen ve stručnosti si řekněme, že se jedná o jednu z (dnes již velkého množství) implementací Pythonu, která je založena na interpretru (podobně jako klasický CPython) zkombinovaném s just-in-time překladačem (JITem). Samotný projekt PyPy je přitom z velké části napsán ve dvou jazycích – běžném Pythonu a navíc i v RPythonu, což je programovací jazyk, jehož základní vlastnosti si dnes představíme. RPython získal své jméno ze sousloví „Restricted Python“, přičemž všechna omezení, která RPython zavádí, jsou pečlivě vybrána z toho důvodu, aby se zjednodušila či vůbec umožnila analýza ®pythonovského kódu, který je překládán do nativního (objektového) kódu.

Tím se dostáváme k základní vlastnosti RPythonu – ten totiž není interpretován, ale skutečně překládán do objektového kódu a z výsledku se vytváří spustitelná aplikace. Celý proces zpracování je poměrně komplikovaný a zdlouhavý, ale ve stručnosti ho lze popsat několika body:

  1. Na vstupu se nachází zdrojové kódy naprogramované v RPythonu (.py)
  2. Nejprve je ze zdrojových kódů vytvořen bajtkód (.pyc)
  3. Z bajtkódu se vygeneruje CFG a ten se následně analyzuje
  4. Grafovou strukturou se prochází a mj. se provádí anotace proměnných (odvození typů, viditelnosti apod.)
  5. Následuje několik konverzí, které se mohou lišit podle toho, pro jakou platformu se generuje výsledek a jaké optimalizace jsou povoleny (inlining, rozbalení smyček atd.)
  6. Dále se do mezivýsledku vkládají i instrukce pro GC
  7. Z grafové struktury se vygeneruje zdrojový kód pro výslednou platformu (typicky pro překladače ANSI C, což je platforma, která nás zajímá dnes)
  8. Pro naši platformu: překladačem ANSI C se provede překlad do objektového kódu
  9. Nakonec se provede slinkování a vytvoření výsledného spustitelného souboru (nebo dynamické knihovny)

2. Použití RPythonu

S RPythonem jsme se již nepřímo setkali v článku Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi. A skutečně – RPython se typicky používá pro psaní interpretrů dalších programovacích jazyků, protože pro toto použití nabízí platforma PyPy dobré nástroje. Ve skutečnosti je však možné RPython použít i pro vlastní projekty, ovšem musíme se smířit s některými omezeními a problémy při snaze používat celý ekosystém postavený okolo Pythonu. Ovšem například v situaci, kdy máme naprogramován a odladěn nějaký výpočet v Pythonu může být zajímavé ho přeložit RPythonem a využít tak toho, že výsledek je relativně dobře optimalizovaný nativní kód (ostatně se můžete podívat na benchmark popsaný níže).

3. Instalace nástroje RPython

Pro instalaci RPythonu je možné použít nástroj pip, a to konkrétně pip2 ve verzi určené pro Python 2.x a nikoli pip3, který je určený pro Python 3 (v tomto případě by se totiž již při instalaci hlásily chyby kvůli nekompatibilitě Python 2 vs. Python 3). Samotný proces instalace není časově náročný, protože na většině počítačů jsou již nainstalovány všechny závislé balíky, především překladač ANSI C:

$ pip install --user rpython
 
Collecting rpython
...
...
...
Installing collected packages: py, pytest, rpython
Successfully installed py-1.5.3 pytest-2.9.2 rpython-0.2.1

Základní kontrola instalace:

$ rpython
 
RPython translation usage:
 
rpython <translation options> target <targetoptions>
 
run with --help for more information

4. První demonstrační příklad: „Hello world!“ připravený pro RPython

Ukažme si nyní, jak vypadá klasický příklad, který se dnes již tradičně (vlastně už od dob slavné The C Programming Language) objevuje v prakticky každé knížce popisující nový programovací jazyk. Jedná se samozřejmě o program vypisující na obrazovku „Hello world!“. V klasickém Pythonu by se jednalo o jednořádkový problém:

print("Hello world!")

V RPythonu to ovšem není tak jednoduché, protože se v něm, na rozdíl od klasického interpretru, ostře rozlišují dvě fáze spuštění kódu:

  1. Fáze překladu (compile time)
  2. Fáze běhu (runtime)

Ve fázi překladu se (poněkud zjednodušeně řečeno) spouští funkce nazvaná target (definice), v níž může programátor ovlivnit způsob překladu, předat další parametry tzv. driveru apod. Funkce target v tom nejjednodušším případě vrací dvě hodnoty: především referenci na funkci, která se má spustit v čase běhu programu (runtime) a typy argumentů předané této funkci (vstupní bod do programu). Funkce spouštěná v runtime bývá tradičně pojmenována entry_point. Celý zdrojový kód je tedy poněkud složitější a navíc je chování obou v něm definovaných funkcí zcela odlišné (compile time × runtime):

def entry_point(argv):
    print "Hello world!"
    return 0
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None
Poznámka: povšimněte si, že pro jistotu používám syntaxi Pythonu 2.x.

5. Překlad demonstračního příkladu do nativního kódu

Překlad demonstračního příkladu, jehož zdrojový kód jsme si uvedli v předchozí kapitole, se provede takto:

$ rpython rpython_basic.py

Celý překlad je rozdělen na několik fází zmíněných v navazujících kapitolách. Navíc se v žádném případě nejedná o rychlou operaci, protože například na mém postarším notebooku s mikroprocesorem i5 (čtyřjádro) se všechny fáze provedly v 25 sekundách, což skutečně není nezanedbatelná doba (je to jedna z věcí, která vás může od použití RPythonu odradit). Na začátku výpisu si povšimněte zprávy „*** target ***“, kterou jsme vložili do funkce target:

[translation:info] 2.7.6 (default, Nov 23 2017, 15:49:48)
[GCC 4.8.4]
[platform:msg] Set platform with 'host' cc=None, using cc='gcc', version='Unknown'
[translation:info] Translating target as defined by rpython_basic
*** target ***
...
...
...
starting source_c
[c:writing] structdef.h
[c:writing] forwarddecl.h
[c:writing] preimpl.h
[c:writing] data_rpython_flowspace.c
[c:writing] data_rpython_memory_gc.c
[c:writing] data_rpython_memory_gctransform.c
[c:writing] data_rpython_rlib.c
[c:writing] data_rpython_rtyper.c
[c:writing] data_rpython_rtyper_lltypesystem.c
[c:writing] data_rpython_translator_c.c
[c:writing] nonfuncnodes.c
[c:writing] data_rpython_memory_gc_1.c
[c:writing] data_rpython_rlib_1.c
[c:writing] data_rpython_rtyper_lltypesystem_1.c
[c:writing] implement.c
[c:writing] rpython_flowspace.c
[c:writing] rpython_memory.c
[c:writing] rpython_memory_gc.c
[c:writing] rpython_memory_gctransform.c
[c:writing] rpython_rlib.c
[c:writing] rpython_rtyper.c
[c:writing] rpython_rtyper_lltypesystem.c
[c:writing] rpython_translator.c
[c:writing] rpython_translator_c.c
[translation:info] written: /tmp/usession-unknown-5/testing_1/testing_1.c
[5ff] translation-task}
[translation:info] Compiling c source...
[5ff] {translation-task
starting compile_c
[platform:execute] make -j 3 in /tmp/usession-unknown-5/testing_1
[translation:info] created: /home/tester/temp/rpython_basic-c
[600] translation-task}
[translation:info] usession directory: /tmp/usession-unknown-5
[Timer] Timings:
[Timer] annotate                       ---  6.1 s
[Timer] rtype_lltype                   ---  0.5 s
[Timer] backendopt_lltype              ---  0.2 s
[Timer] stackcheckinsertion_lltype     ---  0.0 s
[Timer] database_c                     --- 12.8 s
[Timer] source_c                       ---  1.6 s
[Timer] compile_c                      ---  4.0 s
[Timer] =========================================
[Timer] Total:                         --- 25.1 s
Poznámka: ve skutečnosti je výpis mnohem delší, uvedl jsem jen jeho závěr s výslednými časy.

6. Výsledek překladu

V adresáři, z něhož jsme spustili RPython, by se měl po již zmíněných přibližně 25 sekundách objevit nový spustitelný soubor nazvaný rpython_basic-c. Ihned si ho samozřejmě můžeme otestovat:

$ ./rpython_basic-c
 
Hello world!

Popř. se ujistit, že se skutečně jedná o nativní (spustitelný) soubor:

$ file rpython_basic-c
 
rpython_basic-c: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=e7d1b7a4c9612a82abc07f29752e9d29e4f83ab7, not stripped

Samotný soubor je relativně velký (například v porovnání s „Hello world!“ napsaným v céčku), ovšem na tomto místě je zapotřebí říct, že jeho velikost poroste s rozsáhlejší aplikací poměrně pomalu. Důvodem pro tento poměrně velký objem je přidání GC a dalších nástrojů do binárního výsledku:

$ ls -l rpython_basic-c
 
-rwxr-xr-x 1 tester tester 275126 čen  9 20:58 rpython_basic-c

Nepatrně pomůže utilitka strip:

$ strip rpython_basic-c 

S výslednou velikostí přibližně 210 kB:

$ ls -l rpython_basic-c
 
-rwxr-xr-x 1 tester tester 218224 čen  9 21:01 rpython_basic-c
Poznámka: v adresáři /tmp se při překladu vytváří podadresáře s mezisoubory (*.c, *.o atd.). Tyto podadresáře jsou poměrně rozsáhlé, takže si nezapomeňte nastavit systém tak, aby je postupně promazával, popř. je „likvidujte“ sami. V těchto podadresářích naleznete i další generované soubory, například flow diagram apod.

7. Ukončení překladu ve funkci target

Ve čtvrté kapitole jsme si řekli, že funkce target je volaná v čase překladu (compile time) a typicky se používá pro ovlivnění vlastního překladu i pro specifikaci vstupního bodu do výsledného programu. Co se ovšem stane, když v této funkci jednoduše zavoláme exit? Můžeme si to samozřejmě vyzkoušet. Celý program zkrátíme na jedinou funkci:

def target(driver, args):
    print "*** target ***"
    exit(0)

A vyzkoušíme překlad:

$ rpython rpython_stop_compile.py
 
[version:WARNING] Errors getting Mercurial information: Not running from a Mercurial repository!
[translation:info] 2.7.6 (default, Nov 23 2017, 15:49:48)
[GCC 4.8.4]
[platform:msg] Set platform with 'host' cc=None, using cc='gcc', version='Unknown'
[translation:info] Translating target as defined by rpython_stop_compile
*** target ***

Vidíme, že překlad byl skutečně ukončen již v jeho počáteční fázi.

8. Volba jiné funkce použité v runtime pro vstup do programu

Funkce, která bude v čase běhu (runtime) použita jako vstup do výsledného programu, se samozřejmě nemusí jmenovat entry_point, ale můžeme si ji pojmenovat zcela libovolně; samozřejmě ovšem tak, aby odpovídala jmenným konvencím Pythonu:

def muj_vstupni_bod_do_aplikace(argv):
    print "Hello world!"
    return 0
 
def target(driver, args):
    print "*** target ***"
    return muj_vstupni_bod_do_aplikace, None
Poznámka: nemusíte si vůbec dělat starosti se jmennými konvencemi, resp. mezi jejich rozdíly v Pythonu a C. Tyto rozdíly transpřekladač automaticky vyřeší.

9. Zpracování argumentů předaných z příkazového řádku v runtime

Pro úplnost se ještě podívejme na způsob zpracování argumentů (přepínačů atd.), které jsou aplikaci předány v době běhu (runtime). V tomto případě použijeme argv s nímž se pracuje naprosto stejně jako s jakoukoli jinou sekvencí. S dalším použitím se setkáme při popisu benchmarků o několik kapitol níže:

def entry_point(argv):
    print "Command line arguments:"
    for arg in argv:
        print arg
    return 0
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None

Pokud nyní aplikaci přeložíme a následně spustíme výsledný binární soubor s několika parametry (libovolnými):

$ ./rpython_cli_arguments-c prvni druhy --help --foo=bar

Získáme tento výsledek:

Command line arguments:
./rpython_cli_arguments-c
prvni
druhy
--help

10. Analýza bajtkódu a „líný“ překlad

Velmi zajímavou vlastností RPythonu je to, že provádí takzvaný „líný“ překlad (což není přesný terminus technicus, ale dobře se pamatuje :-). Díky tomu, že RPython nejprve přeloží Pythonovský zdrojový kód do bajtkódu a následně analyzuje tento bajtkód, dokáže (většinou) zkonstruovat orientovaný graf, v němž je jednoznačně určeno vzájemné volání funkcí. Zjednodušený graf může vypadat následovně:

Obrázek 1: Graf volání funkcí.

Ve skutečnosti je možné graf prohlížet interaktivně po zadání volby –view, což si popíšeme příště.

Z grafu je možné vyčíst všechny funkce, které jsou volány. To vlastně znamená, že ty funkce, které v grafu nenalezneme, nejsou volány a tudíž se nemusí překládat. Zda je tomu skutečně tak si můžeme snadno otestovat na následujícím příkladu, v němž jsou definovány tři funkce tisknoucí dlouhé řetězce. Ovšem ve skutečnosti se volá jen jediná z těchto funkcí:

def foo():
    print "FOO FOO FOO FOO FOO FOO FOO FOO FOO"
 
 
def bar():
    print "BAR BAR BAR BAR BAR BAR BAR BAR BAR"
 
 
def baz():
    print "BAZ BAZ BAZ BAZ BAZ BAZ BAZ BAZ BAZ"
 
 
def entry_point(argv):
    print "Hello world!"
    foo()
    return 0
 
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None

Po překladu zdrojového kódu získáme spustitelný soubor nazvaný rpython_lazy_compile a můžeme ho prozkoumat, například jednoduchou utilitkou strings určenou pro hledání řetězců:

$ strings rpython_lazy_compile-c | grep FOO
FOO FOO FOO FOO FOO FOO FOO FOO FOO
 
$ strings rpython_lazy_compile-c | grep BAR
 
$ strings rpython_lazy_compile-c | grep BAZ
 

Vidíme, že se našel jen první řetězec, zatímco další dva nejsou do výsledného binárního souboru přidány (a ani těla příslušných funkcí).

Samozřejmě si vše můžeme vyzkoušet na složitějším příkladu se třemi volanými funkcemi tisknoucími řetězec a třemi funkcemi, které se nikde volat nebudou:

def foo():
    print "FOO FOO FOO FOO FOO FOO FOO FOO FOO"
    bar()
    baz()
 
 
def bar():
    print "BAR BAR BAR BAR BAR BAR BAR BAR BAR"
    baz()
 
 
def baz():
    print "BAZ BAZ BAZ BAZ BAZ BAZ BAZ BAZ BAZ"
 
 
def aaa():
    print "AAA AAA AAA AAA AAA AAA AAA AAA AAA"
 
 
def bbb():
    print "BBB BBB BBB BBB BBB BBB BBB BBB BBB"
 
 
def ccc():
    print "CCC CCC CCC CCC CCC CCC CCC CCC CCC"
 
 
def entry_point(argv):
    print "Hello world!"
    foo()
    return 0
 
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None
RPython ve skutečnosti dokáže zpracovat i funkce volané v podmíněné větvi atd. To ovšem klade větší nároky na samotného programátora, protože všechny podmínky musí mít známé typy operandů, které do nich vstupují apod.

11. Základní omezení kladená RPythonem

RPython získal své jméno ze sousloví „Restricted Python“, protože některé konstrukce buď vůbec nepodporuje, nebo pouze v takové podobě, aby byl umožněn korektní překlad do nativního kódu. Jedním z omezení je, že v každém řídicím bodu programu, například těsně pod spojením dvou větví, musí být zcela jednoznačný typ hodnot uložených do proměnných. Zatímco klasický Python je v tomto ohledu velmi dynamický, následující kód není možné v RPythonu použít:

def entry_point(argv):
    if len(argv) == 1:
        x = "one"
    else:
        x = 42
    print x
    return 0
 
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None

Při pokusu o překlad se zobrazí chybové hlášení:

[translation:ERROR] UnionError:
 
Offending annotations:
  SomeInteger(const=0, knowntype=int, nonneg=True, unsigned=False)
  SomeString(const='one', no_nul=True)
 
In <FunctionGraph of (rpython_types:1)entry_point at 0x7f6742ec1750>:
Happened at file rpython_types.py line 6
 
        print x

Naproti tomu je následující kód přeložitelný, a to proto, že se jedna větev už při překladu eliminuje (compile-time optimization), takže RPython ví, že k žádnému větvení vůbec nedošlo:

def entry_point(argv):
    if True:
        x = "one"
    else:
        x = 42
    print x
    return 0
 
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None

12. Chování globálních proměnných

Globální proměnné se chovají jako konstanty, resp. přesněji řečeno RPython hlídá, zda se je nesnažíme přímo měnit. Příklad kódu, který je korektní v Pythonu, ovšem RPython ho nepřeloží:

x = 42
 
def entry_point(argv):
    global x
    print x
    x *= 1
    return 0
 
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None

Chyba při překladu:

[translation:ERROR] FlowingError:
 
Attempting to modify global variable  'x'.
 
In <FunctionGraph of (rpython_globals:3)entry_point at 0x7fac55b30810>:
Happened at file rpython_globals.py line 6
 
        x *= 1

Totéž omezení platí i ve chvíli, kdy se budeme snažit měnit nikoli hodnotu navázanou na proměnnou, ale stav této hodnoty (například seznamu). Následující kód se sice přeloží (neměl by :-), ovšem po spuštění získáme nekonzistentní výsledek (v našem případě dva prázdné seznamy):

x = []
 
def entry_point(argv):
    global x
    print x
    x.append(10)
    print x
    return 0
 
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None

Spuštění:

$ ./rpython_globals_2-c
 
[]
[]

Další omezení si popíšeme příště.

13. Rychlost výsledného nativního kódu produkovaného RPythonem

Jedním z hlavních důvodů, proč může být u některých projektů vůbec užitečné uvažovat o praktickém použití RPythonu, je relativně velký výpočetní výkon výsledného nativního kódu. Ten sice většinou nedosahuje rychlosti dosažené při přepsání celé aplikace do jazyka C nebo C++ popř. do Rustu, jehož překladač se neustále vylepšuje (o assembleru ani nemluvě, to však již vyžaduje velké znalosti cílové architektury), ovšem výhody jazyka odvozeného od vysokoúrovňového Pythonu mohou v mnoha případech převažovat. Klasický CPython, a to jak verze 2.x tak i 3.x, totiž skutečně velkou rychlostí výsledného interpretovaného kódu neoplývá a například Jython je v mnoha případech dokonce ještě pomalejší než CPython, což může být překvapující, neboť v Jythonu se provádí překlad do bajtkódu JVM, který je průběžně JITován (a JIT v JVM vůbec není špatný).

Tento problém se většinou týká programů, které jsou zaměřeny na výpočty; u typických serverových aplikací s databází, messagingem atd. se jedná o méně závažné omezení (zde více záleží na tom, jak se nám podaří odstranit potřebu synchronizace vláken). Zkusme si nyní vytvořit prozatím velmi jednoduchý (mikro)benchmark určený pro porovnání rychlosti Jythonu, CPythonu 2.x, CPythonu 3.x a RPythonu se zaměřením na výpočty (další benchmarky se zpracováním datových struktur budou popsány příště). Všechny benchmarky byly spuštěny na Fedoře 27 a použity byly následující verze Pythonu:

  1. Jython 2.7.0
  2. Python 2.7.14
  3. Python 3.6.3
Poznámka: celý benchmark je napsán takovým způsobem, aby nevyžadoval použití externích knihoven. V praxi bychom ovšem mohli použít například numpy, čímž by se část výpočtu automaticky prováděla v nativním kódu (ostatně právě vylepšená podpora numpy v PyPy a tím pádem i v RPythonu je součástí vývoje další verze PyPy).
Poznámka2: pro zajímavost ještě benchmark přepíšeme do ANSI C a porovnáme rychlost běhu této implementace.

14. Příprava jednoduchého mikrobenchmarku orientovaného na numerické výpočty

Vzhledem k tomu, že se v dnešním benchmarku budeme do značné míry snažit vyhnout měření rychlosti knihovních funkcí, bude celý benchmark skutečně provádět prakticky jen výpočty s výpisem výsledku výpočtů na standardní výstup. Ten bude přesměrován do souboru, protože výsledkem výpočtů budou bitmapy ve formátu Portable Pixel Map (viz [1]). Samozřejmě, že i výpis hodnot na standardní výstup znamená nutnost volání knihovních funkcí, ovšem oproti počtu numerických operací se bude jednat o minimální čas, což je možné zjistit například profilerem, popř. úplným zákazem výstupu (to však nechceme – musíme i optimalizující překladač donutit, aby volání funkcí z kódu zcela neodstranil).

Poznámka: ukazuje se, že čas strávený I/O operacemi se viditelně projeví prakticky jen u RPythonu, protože ostatní interpretry jsou tak pomalé, že I/O tvoří nepatrné procento celkového času.

Celý benchmark spočívá ve výpočtu barev pixelů Mandelbrotovy množiny, přičemž rozlišení výsledného rastrového obrázku i maximální počet iterací bude možné zvolit z příkazového řádku. Následuje výpis zdrojového kódu benchmarku (kód je napsán tak, aby byl kompatibilní s Pythonem 2.x, Pythonem 3.x i Jythonem, ovšem prozatím nikoli s RPythonem):

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import palette_mandmap
from sys import argv, exit
 
 
def calc_mandelbrot(width, height, maxiter, palette):
    print("P3")
    print("{w} {h}".format(w=width, h=height))
    print("255")
 
    cy = -1.5
    for y in range(0, height):
        cx = -2.0
        for x in range(0, width):
            zx = 0.0
            zy = 0.0
            i = 0
            while i < maxiter:
                zx2 = zx * zx
                zy2 = zy * zy
                if zx2 + zy2 > 4.0:
                    break
                zy = 2.0 * zx * zy + cy
                zx = zx2 - zy2 + cx
                i += 1
 
            r = palette[i][0]
            g = palette[i][1]
            b = palette[i][2]
            print("{r} {g} {b}".format(r=r, g=g, b=b))
            cx += 3.0/width
        cy += 3.0/height
 
 
if __name__ == "__main__":
    if len(argv) < 4:
        print("usage: python mandelbrot width height maxiter")
        exit(1)
 
    width = int(argv[1])
    height = int(argv[2])
    maxiter = int(argv[3])
    calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)

V benchmarku se používá i další modul nazvaný palette_mandmap.py, který obsahuje barvovou paletu (palette, color map). Paleta byla získána ze známého (a dnes již vlastně historického) programu Fractint a obsahuje 256 trojic hodnot R, G, B. Samotná paleta nemá prakticky žádný vliv na naměřené hodnoty, ale výsledné obrázky jsou díky ní hezčí:

# taken from Fractint
palette = (
        (255, 255, 255), (224, 224, 224), (216, 216, 216), (208, 208, 208),
        (200, 200, 200), (192, 192, 192), (184, 184, 184), (176, 176, 176),
        (168, 168, 168), (160, 160, 160), (152, 152, 152), (144, 144, 144),
        (136, 136, 136), (128, 128, 128), (120, 120, 120), (112, 112, 112),
        (104, 104, 104),  (96,  96,  96),  (88,  88,  88),  (80,  80,  80),
        (72,   72,  72),  (64,  64,  64),  (56,  56,  56),  (48,  48,  56),
        (40,   40,  56),  (32,  32,  56),  (24,  24,  56),  (16,  16,  56),
        (8,     8,  56), (000, 000,  60), (000, 000,  64), (000, 000,  72),
        (000, 000,  80), (000, 000,  88), (000, 000,  96), (000, 000, 104),
        (000, 000, 108), (000, 000, 116), (000, 000, 124), (000, 000, 132),
        (000, 000, 140), (000, 000, 148), (000, 000, 156), (000, 000, 160),
        (000, 000, 168), (000, 000, 176), (000, 000, 184), (000, 000, 192),
        (000, 000, 200), (000, 000, 204), (000, 000, 212), (000, 000, 220),
        (000, 000, 228), (000, 000, 236), (000, 000, 244), (000, 000, 252),
        (000,   4, 252),   (4,  12, 252),   (8,  20, 252),  (12,  28, 252),
        (16,   36, 252),  (20,  44, 252),  (20,  52, 252),  (24,  60, 252),
        (28,   68, 252),  (32,  76, 252),  (36,  84, 252),  (40,  92, 252),
        (40,  100, 252),  (44, 108, 252),  (48, 116, 252),  (52, 120, 252),
        (56,  128, 252),  (60, 136, 252),  (60, 144, 252),  (64, 152, 252),
        (68,  160, 252),  (72, 168, 252),  (76, 176, 252),  (80, 184, 252),
        (80,  192, 252),  (84, 200, 252),  (88, 208, 252),  (92, 216, 252),
        (96,  224, 252), (100, 232, 252), (100, 228, 248),  (96, 224, 244),
        (92,  216, 240),  (88, 212, 236),  (88, 204, 232),  (84, 200, 228),
        (80,  192, 220),  (76, 188, 216),  (76, 180, 212),  (72, 176, 208),
        (68,  168, 204),  (64, 164, 200),  (64, 156, 196),  (60, 152, 188),
        (56,  144, 184),  (52, 140, 180),  (52, 132, 176),  (48, 128, 172),
        (44,  120, 168),  (40, 116, 160),  (40, 108, 156),  (36, 104, 152),
        (32,   96, 148),  (28,  92, 144),  (28,  84, 140),  (24,  80, 136),
        (20,   72, 128),  (16,  68, 124),  (16,  60, 120),  (12,  56, 116),
        (8,    48, 112),   (4,  44, 108), (000,  36, 100),   (4,  36, 104),
        (12,   40, 108),  (16,  44, 116),  (24,  48, 120),  (28,  52, 128),
        (36,   56, 132),  (40,  60, 140),  (48,  64, 144),  (52,  64, 148),
        (60,   68, 156),  (64,  72, 160),  (72,  76, 168),  (76,  80, 172),
        (84,   84, 180),  (88,  88, 184),  (96,  92, 192), (104, 100, 192),
        (112, 112, 196), (124, 120, 200), (132, 132, 204), (144, 140, 208),
        (152, 152, 212), (164, 160, 216), (172, 172, 220), (180, 180, 224),
        (192, 192, 228), (200, 200, 232), (212, 212, 236), (220, 220, 240),
        (232, 232, 244), (240, 240, 248), (252, 252, 252), (252, 240, 244),
        (252, 224, 232), (252, 208, 224), (252, 192, 212), (252, 176, 204),
        (252, 160, 192), (252, 144, 184), (252, 128, 172), (252, 112, 164),
        (252,  96, 152), (252,  80, 144), (252,  64, 132), (252,  48, 124),
        (252,  32, 112), (252,  16, 104), (252, 000,  92), (236, 000,  88),
        (228, 000,  88), (216,   4,  84), (204,   4,  80), (192,   8,  76),
        (180,   8,  76), (168,  12,  72), (156,  16,  68), (144,  16,  64),
        (132,  20,  60), (124,  20,  60), (112,  24,  56), (100,  24,  52),
        (88,   28,  48),  (76,  32,  44),  (64,  32,  44),  (52,  36,  40),
        (40,   36,  36),  (28,  40,  32),  (16,  44,  28),  (20,  52,  32),
        (24,   60,  36),  (28,  68,  44),  (32,  76,  48),  (36,  88,  56),
        (40,   96,  60),  (44, 104,  64),  (48, 112,  72),  (52, 120,  76),
        (56,  132,  84),  (48, 136,  84),  (40, 144,  80),  (52, 148,  88),
        (68,  156, 100),  (80, 164, 112),  (96, 168, 124), (108, 176, 136),
        (124, 184, 144), (136, 192, 156), (152, 196, 168), (164, 204, 180),
        (180, 212, 192), (192, 220, 200), (208, 224, 212), (220, 232, 224),
        (236, 240, 236), (252, 248, 248), (252, 252, 252), (252, 252, 240),
        (252, 252, 228), (252, 252, 216), (248, 248, 204), (248, 248, 192),
        (248, 248, 180), (248, 248, 164), (244, 244, 152), (244, 244, 140),
        (244, 244, 128), (244, 244, 116), (240, 240, 104), (240, 240,  92),
        (240, 240,  76), (240, 240,  64), (236, 236,  52), (236, 236,  40),
        (236, 236,  28), (236, 236,  16), (232, 232,   0), (232, 232,  12),
        (232, 232,  28), (232, 232,  40), (236, 236,  56), (236, 236,  68),
        (236, 236,  84), (236, 236,  96), (240, 240, 112), (240, 240, 124),
        (240, 240, 140), (244, 244, 152), (244, 244, 168), (244, 244, 180),
        (244, 244, 196), (248, 248, 208), (248, 248, 224), (248, 248, 236),
        (252, 252, 252), (248, 248, 248), (240, 240, 240), (232, 232, 232))

15. Úprava kódu takovým způsobem, aby byl kompatibilní s RPythonem

Programový kód původně připravený pro Python nepůjde přímo použít v RPythonu. Změn, které je nutné provést, je hned několik, i když nejsou příliš zásadní (alespoň v případě našeho benchmarku). Samozřejmě se bude odlišovat především samotný vstupní bod do aplikace, který bude vypadat takto:

def entry_point(argv):
    if len(argv) < 4:
        print("usage: ./mandelbrot width height maxiter")
        return 1
 
    width = int(argv[1])
    height = int(argv[2])
    maxiter = int(argv[3])
    calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)
    return 0

Deklarace vstupního bodu – to již dobře známe:

def target(driver, args):
    print "*** target ***"
    return entry_point, None

Dále jsou úpravy nepatrné, pouze změníme funkci print za příkaz print a odstraníme formátování výstupu (to sice jde udělat, ale nepatrně komplikovaněji):

def calc_mandelbrot(width, height, maxiter, palette):
    print "P3"
    print width, height
    print "255"
    ...
    ...
    ...
            r = palette[i][0]
            g = palette[i][1]
            b = palette[i][2]
            print r, g, b
    ...
    ...
    ...

Výsledný zdrojový kód připravený pro překlad s využitím RPythonu vypadá následovně:

# vim: set fileencoding=utf-8
 
import palette_mandmap
from sys import argv, exit
 
 
def calc_mandelbrot(width, height, maxiter, palette):
    print "P3"
    print width, height
    print "255"
 
    cy = -1.5
    for y in range(0, height):
        cx = -2.0
        for x in range(0, width):
            zx = 0.0
            zy = 0.0
            i = 0
            while i < maxiter:
                zx2 = zx * zx
                zy2 = zy * zy
                if zx2 + zy2 > 4.0:
                    break
                zy = 2.0 * zx * zy + cy
                zx = zx2 - zy2 + cx
                i += 1
 
            r = palette[i][0]
            g = palette[i][1]
            b = palette[i][2]
            print r, g, b
            cx += 3.0/width
        cy += 3.0/height
 
 
def entry_point(argv):
    if len(argv) < 4:
        print("usage: ./mandelbrot width height maxiter")
        return 1
 
    width = int(argv[1])
    height = int(argv[2])
    maxiter = int(argv[3])
    calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)
    return 0
 
 
def target(driver, args):
    print "*** target ***"
    return entry_point, None

Zajímavé je, že RPython neumožňuje indexování prvků v n-tici ve chvíli, kdy je index neznámý v čase překladu. Jinými slovy si RPython není jistý, jestli bude index za všech okolností korektní (0..velikost n-tice) a proto je nutné změnit i kód s paletou. Zde je řešení jednoduché – namísto n-tice použijeme starý dobrý seznam, jehož prvky ovšem již mohou být n-tice, protože ty ve zdrojovém indexujeme přímo indexy 0, 1 a 2 (což RPython pozná):

# taken from Fractint
palette = [
        (255, 255, 255), (224, 224, 224), (216, 216, 216), (208, 208, 208),
        (200, 200, 200), (192, 192, 192), (184, 184, 184), (176, 176, 176),
        (168, 168, 168), (160, 160, 160), (152, 152, 152), (144, 144, 144),
        (136, 136, 136), (128, 128, 128), (120, 120, 120), (112, 112, 112),
        (104, 104, 104),  (96,  96,  96),  (88,  88,  88),  (80,  80,  80),
        (72,   72,  72),  (64,  64,  64),  (56,  56,  56),  (48,  48,  56),
        (40,   40,  56),  (32,  32,  56),  (24,  24,  56),  (16,  16,  56),
        (8,     8,  56), (000, 000,  60), (000, 000,  64), (000, 000,  72),
        (000, 000,  80), (000, 000,  88), (000, 000,  96), (000, 000, 104),
        (000, 000, 108), (000, 000, 116), (000, 000, 124), (000, 000, 132),
        (000, 000, 140), (000, 000, 148), (000, 000, 156), (000, 000, 160),
        (000, 000, 168), (000, 000, 176), (000, 000, 184), (000, 000, 192),
        (000, 000, 200), (000, 000, 204), (000, 000, 212), (000, 000, 220),
        (000, 000, 228), (000, 000, 236), (000, 000, 244), (000, 000, 252),
        (000,   4, 252),   (4,  12, 252),   (8,  20, 252),  (12,  28, 252),
        (16,   36, 252),  (20,  44, 252),  (20,  52, 252),  (24,  60, 252),
        (28,   68, 252),  (32,  76, 252),  (36,  84, 252),  (40,  92, 252),
        (40,  100, 252),  (44, 108, 252),  (48, 116, 252),  (52, 120, 252),
        (56,  128, 252),  (60, 136, 252),  (60, 144, 252),  (64, 152, 252),
        (68,  160, 252),  (72, 168, 252),  (76, 176, 252),  (80, 184, 252),
        (80,  192, 252),  (84, 200, 252),  (88, 208, 252),  (92, 216, 252),
        (96,  224, 252), (100, 232, 252), (100, 228, 248),  (96, 224, 244),
        (92,  216, 240),  (88, 212, 236),  (88, 204, 232),  (84, 200, 228),
        (80,  192, 220),  (76, 188, 216),  (76, 180, 212),  (72, 176, 208),
        (68,  168, 204),  (64, 164, 200),  (64, 156, 196),  (60, 152, 188),
        (56,  144, 184),  (52, 140, 180),  (52, 132, 176),  (48, 128, 172),
        (44,  120, 168),  (40, 116, 160),  (40, 108, 156),  (36, 104, 152),
        (32,   96, 148),  (28,  92, 144),  (28,  84, 140),  (24,  80, 136),
        (20,   72, 128),  (16,  68, 124),  (16,  60, 120),  (12,  56, 116),
        (8,    48, 112),   (4,  44, 108), (000,  36, 100),   (4,  36, 104),
        (12,   40, 108),  (16,  44, 116),  (24,  48, 120),  (28,  52, 128),
        (36,   56, 132),  (40,  60, 140),  (48,  64, 144),  (52,  64, 148),
        (60,   68, 156),  (64,  72, 160),  (72,  76, 168),  (76,  80, 172),
        (84,   84, 180),  (88,  88, 184),  (96,  92, 192), (104, 100, 192),
        (112, 112, 196), (124, 120, 200), (132, 132, 204), (144, 140, 208),
        (152, 152, 212), (164, 160, 216), (172, 172, 220), (180, 180, 224),
        (192, 192, 228), (200, 200, 232), (212, 212, 236), (220, 220, 240),
        (232, 232, 244), (240, 240, 248), (252, 252, 252), (252, 240, 244),
        (252, 224, 232), (252, 208, 224), (252, 192, 212), (252, 176, 204),
        (252, 160, 192), (252, 144, 184), (252, 128, 172), (252, 112, 164),
        (252,  96, 152), (252,  80, 144), (252,  64, 132), (252,  48, 124),
        (252,  32, 112), (252,  16, 104), (252, 000,  92), (236, 000,  88),
        (228, 000,  88), (216,   4,  84), (204,   4,  80), (192,   8,  76),
        (180,   8,  76), (168,  12,  72), (156,  16,  68), (144,  16,  64),
        (132,  20,  60), (124,  20,  60), (112,  24,  56), (100,  24,  52),
        (88,   28,  48),  (76,  32,  44),  (64,  32,  44),  (52,  36,  40),
        (40,   36,  36),  (28,  40,  32),  (16,  44,  28),  (20,  52,  32),
        (24,   60,  36),  (28,  68,  44),  (32,  76,  48),  (36,  88,  56),
        (40,   96,  60),  (44, 104,  64),  (48, 112,  72),  (52, 120,  76),
        (56,  132,  84),  (48, 136,  84),  (40, 144,  80),  (52, 148,  88),
        (68,  156, 100),  (80, 164, 112),  (96, 168, 124), (108, 176, 136),
        (124, 184, 144), (136, 192, 156), (152, 196, 168), (164, 204, 180),
        (180, 212, 192), (192, 220, 200), (208, 224, 212), (220, 232, 224),
        (236, 240, 236), (252, 248, 248), (252, 252, 252), (252, 252, 240),
        (252, 252, 228), (252, 252, 216), (248, 248, 204), (248, 248, 192),
        (248, 248, 180), (248, 248, 164), (244, 244, 152), (244, 244, 140),
        (244, 244, 128), (244, 244, 116), (240, 240, 104), (240, 240,  92),
        (240, 240,  76), (240, 240,  64), (236, 236,  52), (236, 236,  40),
        (236, 236,  28), (236, 236,  16), (232, 232,   0), (232, 232,  12),
        (232, 232,  28), (232, 232,  40), (236, 236,  56), (236, 236,  68),
        (236, 236,  84), (236, 236,  96), (240, 240, 112), (240, 240, 124),
        (240, 240, 140), (244, 244, 152), (244, 244, 168), (244, 244, 180),
        (244, 244, 196), (248, 248, 208), (248, 248, 224), (248, 248, 236),
        (252, 252, 252), (248, 248, 248), (240, 240, 240), (232, 232, 232)]

16. Skripty pro spuštění benchmarku se zvoleným interpretrem a parametry

Pro spuštění výše popsaného benchmarku použijeme čtveřici prakticky shodných skriptů, které budou postupně zvětšovat rozlišení výsledného rastrového obrázku. Pro malý počet iterací se tedy bude spíše měřit rychlost nastartování interpretru Pythonu, popř. virtuálního stroje Javy, ovšem u vyšších rozlišení (přibližně od 128×128 pixelů) již začne převládat samotná doba výpočtu a vliv startu interpretru/JVM tak bude jen marginální.

Skript pro Python 2.x

Tento skript pochopitelně vyžaduje nainstalovaný Python 2, což však na prakticky žádné distribuci nebude problém:

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096"
 
OUTFILE="python2.times"
PREFIX="python2"
 
rm $OUTFILE
 
for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" python2 -B mandelbrot.py $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done

Skript pro Python 3.x

Druhý skript používá interpret Pythonu 3:

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096"
 
OUTFILE="python3.times"
PREFIX="python3"
 
rm $OUTFILE
 
for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" python3 -B mandelbrot.py $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done

Skript pro Jython

V pořadí třetí skript pro svoji korektní činnost vyžaduje, aby se v aktuálním adresáři nacházel Java archiv s Jythonem, popř. jen symbolický link na tento archiv:

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096"
 
OUTFILE="jython.times"
PREFIX="jython"
 
rm $OUTFILE
 
for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" java -jar jython-standalone-2.7.0.jar mandelbrot.py $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done
Instalace Jythonu je snadná: postačuje si stáhnout Java archiv z adresy http://search.maven.org/re­motecontent?filepath=org/pyt­hon/jython-standalone/2.7.0/jython-standalone-2.7.0.jar, například takto:
$ wget -O jython-standalone-2.7.0.jar http://search.maven.org/remotecontent?filepath=org/python/jython-standalone/2.7.0/jython-standalone-2.7.0.jar

Skript pro RPython

Aby bylo možné tento skript korektně spustit, musí se v aktuálním adresáři nacházet nativní spustitelný soubor mandelbrot_rpython-c, který vznikl překladem zdrojového kódu mandelbrot_rpython.py, který jsme si popsali v předchozích dvou kapitolách:

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048"
 
OUTFILE="rpyton.times"
PREFIX="rpython"
 
rm $OUTFILE
 
for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" ./mandelbrot_rpython-c $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done

17. Malá odbočka – implementace totožného algoritmu v ANSI C

Jen pro zajímavost (a taktéž kvůli benchmarkům) se podívejme na to, jak je možné totožný algoritmus implementovat v ANSI C. Jedná se z velké části o přímý přepis původního algoritmu bez dalších optimalizací, které céčko umožňuje:

#include <stdlib.h>
#include <stdio.h>
 
#include "palette_mandmap.h"
 
void calc_mandelbrot(unsigned int width, unsigned int height, unsigned int maxiter, unsigned char palette[][3])
{
    puts("P3");
    printf("%d %d\n", width, height);
    puts("255");
 
    double cy = -1.5;
    int y;
    for (y=0; y<height; y++) {
        double cx = -2.0;
        int x;
        for (x=0; x<width; x++) {
            double zx = 0.0;
            double zy = 0.0;
            unsigned int i = 0;
            while (i < maxiter) {
                double zx2 = zx * zx;
                double zy2 = zy * zy;
                if (zx2 + zy2 > 4.0) {
                    break;
                }
                zy = 2.0 * zx * zy + cy;
                zx = zx2 - zy2 + cx;
                i++;
            }
            unsigned char *color = palette[i];
            unsigned char r = *color++;
            unsigned char g = *color++;
            unsigned char b = *color;
            printf("%d %d %d\n", r, g, b);
            cx += 3.0/width;
        }
        cy += 3.0/height;
    }
}
 
 
int main(int argc, char **argv)
{
    if (argc < 4) {
        puts("usage: ./mandelbrot width height maxiter");
        return 1;
    }
    int width = atoi(argv[1]);
    int height = atoi(argv[2]);
    int maxiter = atoi(argv[3]);
    calc_mandelbrot(width, height, maxiter, palette);
    return 0;
}

Paleta je implementována jako prosté dvourozměrné pole:

/* taken from Fractint */
unsigned char palette[][3] = {
        {255, 255, 255}, {224, 224, 224}, {216, 216, 216}, {208, 208, 208},
        {200, 200, 200}, {192, 192, 192}, {184, 184, 184}, {176, 176, 176},
        {168, 168, 168}, {160, 160, 160}, {152, 152, 152}, {144, 144, 144},
        {136, 136, 136}, {128, 128, 128}, {120, 120, 120}, {112, 112, 112},
        {104, 104, 104},  {96,  96,  96},  {88,  88,  88},  {80,  80,  80},
        {72,   72,  72},  {64,  64,  64},  {56,  56,  56},  {48,  48,  56},
        {40,   40,  56},  {32,  32,  56},  {24,  24,  56},  {16,  16,  56},
        {8,     8,  56}, {000, 000,  60}, {000, 000,  64}, {000, 000,  72},
        {000, 000,  80}, {000, 000,  88}, {000, 000,  96}, {000, 000, 104},
        {000, 000, 108}, {000, 000, 116}, {000, 000, 124}, {000, 000, 132},
        {000, 000, 140}, {000, 000, 148}, {000, 000, 156}, {000, 000, 160},
        {000, 000, 168}, {000, 000, 176}, {000, 000, 184}, {000, 000, 192},
        {000, 000, 200}, {000, 000, 204}, {000, 000, 212}, {000, 000, 220},
        {000, 000, 228}, {000, 000, 236}, {000, 000, 244}, {000, 000, 252},
        {000,   4, 252},   {4,  12, 252},   {8,  20, 252},  {12,  28, 252},
        {16,   36, 252},  {20,  44, 252},  {20,  52, 252},  {24,  60, 252},
        {28,   68, 252},  {32,  76, 252},  {36,  84, 252},  {40,  92, 252},
        {40,  100, 252},  {44, 108, 252},  {48, 116, 252},  {52, 120, 252},
        {56,  128, 252},  {60, 136, 252},  {60, 144, 252},  {64, 152, 252},
        {68,  160, 252},  {72, 168, 252},  {76, 176, 252},  {80, 184, 252},
        {80,  192, 252},  {84, 200, 252},  {88, 208, 252},  {92, 216, 252},
        {96,  224, 252}, {100, 232, 252}, {100, 228, 248},  {96, 224, 244},
        {92,  216, 240},  {88, 212, 236},  {88, 204, 232},  {84, 200, 228},
        {80,  192, 220},  {76, 188, 216},  {76, 180, 212},  {72, 176, 208},
        {68,  168, 204},  {64, 164, 200},  {64, 156, 196},  {60, 152, 188},
        {56,  144, 184},  {52, 140, 180},  {52, 132, 176},  {48, 128, 172},
        {44,  120, 168},  {40, 116, 160},  {40, 108, 156},  {36, 104, 152},
        {32,   96, 148},  {28,  92, 144},  {28,  84, 140},  {24,  80, 136},
        {20,   72, 128},  {16,  68, 124},  {16,  60, 120},  {12,  56, 116},
        {8,    48, 112},   {4,  44, 108}, {000,  36, 100},   {4,  36, 104},
        {12,   40, 108},  {16,  44, 116},  {24,  48, 120},  {28,  52, 128},
        {36,   56, 132},  {40,  60, 140},  {48,  64, 144},  {52,  64, 148},
        {60,   68, 156},  {64,  72, 160},  {72,  76, 168},  {76,  80, 172},
        {84,   84, 180},  {88,  88, 184},  {96,  92, 192}, {104, 100, 192},
        {112, 112, 196}, {124, 120, 200}, {132, 132, 204}, {144, 140, 208},
        {152, 152, 212}, {164, 160, 216}, {172, 172, 220}, {180, 180, 224},
        {192, 192, 228}, {200, 200, 232}, {212, 212, 236}, {220, 220, 240},
        {232, 232, 244}, {240, 240, 248}, {252, 252, 252}, {252, 240, 244},
        {252, 224, 232}, {252, 208, 224}, {252, 192, 212}, {252, 176, 204},
        {252, 160, 192}, {252, 144, 184}, {252, 128, 172}, {252, 112, 164},
        {252,  96, 152}, {252,  80, 144}, {252,  64, 132}, {252,  48, 124},
        {252,  32, 112}, {252,  16, 104}, {252, 000,  92}, {236, 000,  88},
        {228, 000,  88}, {216,   4,  84}, {204,   4,  80}, {192,   8,  76},
        {180,   8,  76}, {168,  12,  72}, {156,  16,  68}, {144,  16,  64},
        {132,  20,  60}, {124,  20,  60}, {112,  24,  56}, {100,  24,  52},
        {88,   28,  48},  {76,  32,  44},  {64,  32,  44},  {52,  36,  40},
        {40,   36,  36},  {28,  40,  32},  {16,  44,  28},  {20,  52,  32},
        {24,   60,  36},  {28,  68,  44},  {32,  76,  48},  {36,  88,  56},
        {40,   96,  60},  {44, 104,  64},  {48, 112,  72},  {52, 120,  76},
        {56,  132,  84},  {48, 136,  84},  {40, 144,  80},  {52, 148,  88},
        {68,  156, 100},  {80, 164, 112},  {96, 168, 124}, {108, 176, 136},
        {124, 184, 144}, {136, 192, 156}, {152, 196, 168}, {164, 204, 180},
        {180, 212, 192}, {192, 220, 200}, {208, 224, 212}, {220, 232, 224},
        {236, 240, 236}, {252, 248, 248}, {252, 252, 252}, {252, 252, 240},
        {252, 252, 228}, {252, 252, 216}, {248, 248, 204}, {248, 248, 192},
        {248, 248, 180}, {248, 248, 164}, {244, 244, 152}, {244, 244, 140},
        {244, 244, 128}, {244, 244, 116}, {240, 240, 104}, {240, 240,  92},
        {240, 240,  76}, {240, 240,  64}, {236, 236,  52}, {236, 236,  40},
        {236, 236,  28}, {236, 236,  16}, {232, 232,   0}, {232, 232,  12},
        {232, 232,  28}, {232, 232,  40}, {236, 236,  56}, {236, 236,  68},
        {236, 236,  84}, {236, 236,  96}, {240, 240, 112}, {240, 240, 124},
        {240, 240, 140}, {244, 244, 152}, {244, 244, 168}, {244, 244, 180},
        {244, 244, 196}, {248, 248, 208}, {248, 248, 224}, {248, 248, 236},
        {252, 252, 252}, {248, 248, 248}, {240, 240, 240}, {232, 232, 232}};

Makefile pro překlad (všimněte si, že jsou zapnuty optimalizace, které ovšem provádí i RPython):

# Parametry prekladace.
CFLAGS=-Wall -ansi -O9
 
PROGNAME=mandelbrot
 
all:    $(PROGNAME)
 
clean:
        rm *.o
        rm $(PROGNAME)
 
# Pravidlo pro slinkovani vsech objektovych souboru a vytvoreni
# vysledne spustitelne aplikace.
$(PROGNAME):    $(PROGNAME).o
        $(CC) -o $@ $(LDFLAGS) $<
 
# Pravidlo pro preklad kazdeho zdrojoveho souboru do prislusneho
# objektoveho souboru.
%.o:    %.c
        $(CC) $(CFLAGS) -c $< -o $@

A nakonec skript pro spuštění benchmarku:

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096"
 
OUTFILE="c.times"
PREFIX="c"
 
rm $OUTFILE
 
for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" ./mandelbrot $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done

18. Výsledky benchmarků – porovnání s Pythonem 2, Pythonem 3, Jythonem a ANSI C

Všechny čtyři výše zmíněné skripty byly použity pro spuštění benchmarků a v následující tabulce můžete vidět výsledky, které si posléze okomentujeme (časy jsou uvedeny v sekundách se zaokrouhlením na dvě desetinná místa, proto jsou někde vidět nuly):

Rozlišení CPython 2 CPython 3 Jython RPython ANSI C
16×16 0,01 0,03 2,25 0,00 0,00
24×24 0,01 0,03 1,87 0,00 0,00
32×32 0,02 0,03 1,95 0,00 0,00
48×48 0,03 0,04 2,14 0,00 0,00
64×64 0,05 0,06 2,02 0,00 0,00
96×96 0,09 0,10 2,17 0,01 0,00
128×128 0,16 0,16 2,52 0,02 0.00
192×192 0,34 0,34 2,73 0,05 0.01
256×256 0,57 0,59 2,79 0,07 0.02
384×384 1,27 1,34 3,93 0,16 0.04
512×512 2,26 2,34 5,48 0,29 0.07
768×768 5,08 5,52 9,41 0,65 0.16
1024×1024 9,32 9,69 13,70 1,17 0.29
1536×1536 24,48 21,99 28,50 2,61 0.67
2048×2048 36,27 36,70 54,22 4,62 1.19
3072×3072 84,82 83,41 104,16 10,53 2.68
4096×4096 150,31 152,21 203,18 18,64 4.75

Obrázek 2: Výsledky benchmarku pro všechna rozlišení.

Výsledky tohoto benchmarku mluví jasně – absolutně nejrychlejší je výpočet provedený nativním kódem, který byl vygenerován RPythonen. Naproti tomu nejpomalejší je vždy Jython, který ztrácí přibližně dvě sekundy startem virtuálního stroje Javy, ovšem teoreticky by se měl tento čas pro větší rozlišení amortizovat. Naproti tomu intepretry CPythonu 2 a CPythonu 3 dávají prakticky shodné časy běhu, což ale může být pochopitelné – větších rozdílů se dočkáme u programů manipulujících s řetězci.

Obrázek 3: Vybrané výsledky benchmarku bez prvních několika běhů, které trvají jen krátce.

Důležitá poznámka: všechny výsledné soubory s rastrovými obrázky jsou binárně totožné, tj. žádný z interpretrů ani překladačů neprovedl žádné optimalizace, které by ovlivnily FP operace (fast FP atd.). U překladače ANSI C (například v gcc) si však můžete vyzkoušet efekt přidání přepínače -ffast-math, výsledné porovnání by však již nebylo férové.

19. Repositář s demonstračními příklady

Všechny demonstrační příklady, které jsme si v dnešním článku ukázali, naleznete na adrese https://github.com/tisnik/rpython-examples. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalován RPython a jeho závislosti, především tedy překladač céčka, benchmark používající Jython samozřejmě vyžaduje i tento interpret):

Benchmark napsaný pro Python 2.x, Python 3.x i pro Jython

Benchmark napsaný výhradně pro RPython

Benchmark napsaný v ANSI C

20. Odkazy na Internetu

  1. The Magic of RPython
    https://refi64.com/posts/the-magic-of-rpython.html
  2. RPython: Frequently Asked Questions
    http://rpython.readthedoc­s.io/en/latest/faq.html
  3. RPython’s documentation
    http://rpython.readthedoc­s.io/en/latest/index.html
  4. RPython (Wikipedia)
    https://en.wikipedia.org/wi­ki/PyPy#RPython
  5. Getting Started with RPython
    http://rpython.readthedoc­s.io/en/latest/getting-started.html
  6. PyPy (home page)
    https://pypy.org/
  7. PyPy (dokumentace)
    http://doc.pypy.org/en/latest/
  8. Cython (home page)
    http://cython.org/
  9. Cython (wiki)
    https://github.com/cython/cython/wiki
  10. Cython (Wikipedia)
    https://en.wikipedia.org/wiki/Cython
  11. Cython (GitHub)
    https://github.com/cython/cython
  12. Localized Type Inference of Atomic Types in Python (2005)
    http://citeseer.ist.psu.e­du/viewdoc/summary?doi=10­.1.1.90.3231
  13. Numba
    http://numba.pydata.org/
  14. Tutorial: Writing an Interpreter with PyPy, Part 1
    https://morepypy.blogspot­.com/2011/04/tutorial-writing-interpreter-with-pypy.html
  15. List of numerical analysis software
    https://en.wikipedia.org/wi­ki/List_of_numerical_analy­sis_software
  16. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  17. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
    https://www.root.cz/clanky/pro­gramovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/
  18. The future can be written in RPython now (článek z roku 2010)
    http://blog.christianpero­ne.com/2010/05/the-future-can-be-written-in-rpython-now/
  19. PyPy is the Future of Python (článek z roku 2010)
    https://alexgaynor.net/2010/ma­y/15/pypy-future-python/
  20. Portal:Python programming
    https://en.wikipedia.org/wi­ki/Portal:Python_programming
  21. Python Implementations: Compilers
    https://wiki.python.org/mo­in/PythonImplementations#Com­pilers
  22. RPython Frontend and C Wrapper Generator
    http://www.codeforge.com/ar­ticle/383293
  23. PyPy’s Approach to Virtual Machine Construction
    https://bitbucket.org/pypy/ex­tradoc/raw/tip/talk/dls2006/py­py-vm-construction.pdf
Našli jste v článku chybu?