Obsah
1. RPython: překvapivě výkonný dialekt Pythonu, na němž je založen PyPy
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
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
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 [R]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:
- Na vstupu se nachází zdrojové kódy naprogramované v RPythonu (.py)
- Nejprve je ze zdrojových kódů vytvořen bajtkód (.pyc)
- Z bajtkódu se vygeneruje CFG a ten se následně analyzuje
- Grafovou strukturou se prochází a mj. se provádí anotace proměnných (odvození typů, viditelnosti apod.)
- 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.)
- Dále se do mezivýsledku vkládají i instrukce pro GC
- 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)
- Pro naši platformu: překladačem ANSI C se provede překlad do objektového kódu
- 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:
- Fáze překladu (compile time)
- 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
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
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
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
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í.
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
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:
- Jython 2.7.0
- Python 2.7.14
- Python 3.6.3
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).
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
$ 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.
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
# | Soubor | Adresa |
---|---|---|
1 | mandelbrot_rpython.py | https://github.com/tisnik/rpython-examples/blob/master/benchmarks/mandelbrot/rpython/mandelbrot_rpython.py |
2 | palette_mandmap.py | https://github.com/tisnik/rpython-examples/blob/master/benchmarks/mandelbrot/rpython/palette_mandmap.py |
3 | test_rpython.sh | https://github.com/tisnik/rpython-examples/blob/master/benchmarks/mandelbrot/rpython/test_rpython.sh |
Benchmark napsaný v ANSI C
20. Odkazy na Internetu
- The Magic of RPython
https://refi64.com/posts/the-magic-of-rpython.html - RPython: Frequently Asked Questions
http://rpython.readthedocs.io/en/latest/faq.html - RPython’s documentation
http://rpython.readthedocs.io/en/latest/index.html - RPython (Wikipedia)
https://en.wikipedia.org/wiki/PyPy#RPython - Getting Started with RPython
http://rpython.readthedocs.io/en/latest/getting-started.html - PyPy (home page)
https://pypy.org/ - PyPy (dokumentace)
http://doc.pypy.org/en/latest/ - Cython (home page)
http://cython.org/ - Cython (wiki)
https://github.com/cython/cython/wiki - Cython (Wikipedia)
https://en.wikipedia.org/wiki/Cython - Cython (GitHub)
https://github.com/cython/cython - Localized Type Inference of Atomic Types in Python (2005)
http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.90.3231 - Numba
http://numba.pydata.org/ - Tutorial: Writing an Interpreter with PyPy, Part 1
https://morepypy.blogspot.com/2011/04/tutorial-writing-interpreter-with-pypy.html - List of numerical analysis software
https://en.wikipedia.org/wiki/List_of_numerical_analysis_software - Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/ - Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
https://www.root.cz/clanky/programovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/ - The future can be written in RPython now (článek z roku 2010)
http://blog.christianperone.com/2010/05/the-future-can-be-written-in-rpython-now/ - PyPy is the Future of Python (článek z roku 2010)
https://alexgaynor.net/2010/may/15/pypy-future-python/ - Portal:Python programming
https://en.wikipedia.org/wiki/Portal:Python_programming - Python Implementations: Compilers
https://wiki.python.org/moin/PythonImplementations#Compilers - RPython Frontend and C Wrapper Generator
http://www.codeforge.com/article/383293 - PyPy’s Approach to Virtual Machine Construction
https://bitbucket.org/pypy/extradoc/raw/tip/talk/dls2006/pypy-vm-construction.pdf