Šlo by dodělat i porovnání s pypy?
Zkusil jsem si totiž porovnat rychlost toho benchmarku u sebe na počítači mezi pythonem2/3 a pypy. Překvapuje mě, že poměr rychlosti vychází mezi pythonem a pypy u dvou mnou zkoušených příkladů více než 30× rychlejší (resp. více jak 15× rychlejší) oproti cca 8× rychlejšímu rpythonu v článku (oproti pythonu).
S takovým výsledkem mi přijde zbytečné piplat se s rpythonem, když to stačí spustit v pypy a výsledek je ještě lepší (testoval jsem jen 2 případy).
2048×2048:
Python: 1m9,365s
Python3: 1m13,470s
Pypy: 0m2,191s (31,66× rychlejší resp. 33,53×)
4096×4096:
Python: 4m50,124s
Python3: 4m50,474s
Pypy: 0m18,403s
Můj PC je zřetelně pomalejší než použitý v článku (zhruba poloviční výkon při porovnání běhu pythonu) a přesto je výsledek v běhu u 2048×2048 2× rychlejší než rpython běžící na PC v článku a u 4096×4096 stejně rychlý jako rpython běžící na PC v článku. Čím to?
Ahoj, přesně na to se chystám příště. PyPy sice používá RPython, ale má k dispozici plnohodnotný JIT a ten to může ještě víc zoptimalizovat. Měl bys vidět, že první běhy pro malé rozlišení budou pomalejší a potom - od nějaké hranice - už začne JIT vyhrávat.
Jen pro zajímavost - byly výsledky binárně shodné? Mě JIT optimalizoval výpočty tak, že výsledky byly vlastně přesnější (-ffast-math atd.).
Bez uvedení explicitní informace o typech (a to prakticky všude) byl rozdíl výkonu oproti interpretru dost malý, tuším mezi 30-40%. Přidání anotací o typech argumentů a funkcí to vylepšilo dost výrazně, ale přišlo mi, že je to víc práce, než upravit kód pro RPython nebo jen odladit pro PyPy - ale tady skutečně záleží na konkrétním algoritmu, protože velmi dynamický kód se upravuje strašně špatně (a typové anotace se přidávají taky špatně).
Zkusím se o tom víc rozepsat příště, protože to možná napsáno v jednom odstavci vyznívá, že Cython je špatný. Určitě má své místo, stejně jako Numba.
Vlastně Cython a RPython se snaží řešit podobný problém - rozlišit typy už v době překladu - a každý na to jde trošku jinak (typové anotace vs flow diagram). Oboje má svoje omezení.
Ano, Cython take vnímám jako velice záludný jazyk: jakmile někde nezná typ, zavolá se celá Pythoní mašinérie pro univerzální typ. Je nutné typ deklarovat. Ale nejde tam zapnout kontrolu, že by deklaraci typu vyžadoval.
Navíc občas je schopný si typ domyslet, záleží například na jedné ze dvou syntaxí for cyklu, kterou použijete.
V praxi to znamená že musím často kontrolovat generovaný C kód (zobrazený v pěkném html), abych objevil, kam mi cython nacpal spoustu kódu navíc, který jsem nechtěl, a pak vymyslel, jak mu to rozmluvit.
Cython je dobrý jako interface umožňující zapojení kompilovaných kousků kódu do pythonovského a numpy programu.
Ale kdyby měl dialekt vyžadující deklaraci typů, odstranilo by to mnoho pastí. A kdyby tento kompilovaný kód šlo psát rovnou v C, byla by to dokonce i příjemná práce. Takhle myslím v C, šifruji to do pseudo-pythonu, a třesu se strachy, kde zapomenu deklarovat proměnnou a pak to budu zdlouhavě hledat.
V Cythonu se dobře dostanu k underlying datům numpy array; bojím se, že pro numbu nebo jiné přístupy to není tak propracované a přenos velkých polí mezi C a numpy by vždy zdržoval.
Štěpán
Ahoj, přesně na to se chystám příště
Už se těším...
Měl bys vidět, že první běhy pro malé rozlišení budou pomalejší a potom - od nějaké hranice - už začne JIT vyhrávat.
To se dá předpokládat, že než se spustí s jit, že to trvá a projeví se krátkých běhů více...
Jen pro zajímavost - byly výsledky binárně shodné?
To jsem neřešil, prostě jsem spustil jeden a ten samý .py kód pod python2, python3 a pypy a porovnával délku běhu.
Jen pro zajímavost - byly výsledky binárně shodné?
To jsem neřešil, prostě jsem spustil jeden a ten samý .py kód pod python2, python3 a pypy a porovnával délku běhu.
Ono je to v tomto ohledu zajímavé a Pythoní specifikace to moc myslím vlastně ani neřeší - totiž některé optimalizace FPU operací vedou k tomu, že jsou výsledky přesnější na nějakém n-tém místě, protože se v závislosti na architektuře atd. může počítat s větší přesností, než je double. To vede k tomu, že pár pixelů v obrázku může mít nepatrně odlišnou hodnotu (třeba jen o jedničku). RPython dává naprosto shodné výsledky s CPythonem, ale protože nemáme obdobu strictfp z Javy, tak to vlastně jen znamená to, že se drží původní sémantiky co nejvíc to jde (a některé optimalizace buď neprovádí, nebo přepíná stav FPU). Takže jsem se jen ptal proto, že odlišné výsledky můžou znamenat, že si překladač dovolil optimalizovat ještě trošku drastičtějším způsobem.
Pozor, článek je v současné podobě zavádějící.
Jak se píše v oficiální dokumentaci hned několikrát, RPython primárně NENÍ překladač pythonu do nativního kódu! Vlastními slovy autorů:
RPython is a translation and support framework for producing implementations of dynamic languages, emphasizing a clean separation between language specification and implementation aspects.
V čem je rozdíl? Především v tom, že RPython je zaměřený na tvorbu ostatních jazyků, ne na tvorbu binárek a knihoven.
Například přidává velmi dobrou podporu JITování vašeho projektu, kde je možné označit funkce, které je možné JITovat a on je zJITuje. To přitom v normálním kódu nedává moc smysl, neboť je to celé určené směrem k drcení instrukcí bajtkódu (neustále opakovaný lookup do pole, načtení argumentu, zavolání interpretace a tohle pořád dokola).
Další věc je, že RPython je silně, silně omezený subset pythonu, kde je nutné přes assert isinstance()
dávat type hinty asi tak každých pět řádek, pole můžou obsahovat jen jeden datový typ a tak podobně.
Proto taky píšou ve FAQ vysloveně:
Can RPython compile normal Python programs to C?
No, RPython is not a Python compiler.
In Python, it is mostly impossible to prove anything about the types that a program will manipulate by doing a static analysis. It should be clear if you are familiar with Python, but if in doubt see [BRETT].
If you want a fast Python program, please use the PyPy JIT instead.
Jazyky v RPythonu nejsou implementovány proto, že by byl tak dobrý překladač do binárního kódu, ale právě proto, že nabízí velkou podporu věcí, které dávají smysl jen v interpretrech programovacích jazyků. Například bindingy na numerické operace nad velkými inty. Možnost definovat si vlastní staticky typované dicty. Volit verzi garbage collectoru. Nechat funkci statisticky poměřit, jestli se vyplatí JITovat a tak podobně.
Pokud chcete překladač do C, či nativního kódu, je k dispozici spousta jiných projektů, například Nuitka, Rusthon, grumpy, cython, shedskin, Theano či Parakeet .
Jako někdo, kdo momentálně píše v RPythonu interpret jazyka a historicky zkoušel různé překladače do C a celkově transkompilery (brython například), můžu potvrdit, že ten rozdíl není malý. Pokud to chcete použít jen k "zrychlení python kódu", tak jsou lepší projekty a s rpythonem budete bojovat na každém kroku.
Přepisoval jsem jen asi 700 řádkový parser z čistého pythonu do RPythonu a dalo mi to zabrat několik dní. Nejenom protože jsem z toho musel udělat staticky typovanou verzi programu, ale hlavně a především proto, že RPython nenabízí kvalitní chybové hlášky. Jeho použitím prostě nikdy nemělo být jako překladač do C, takže místo informace o čísle řádku dostanete něco jako
[translation:info] 2.7.10 (5.1.2+dfsg-1~16.04, Jun 16 2016, 17:37:42) [PyPy 5.1.2 with GCC 5.3.1 20160413] [platform:msg] Set platform with 'host' cc=None, using cc='gcc', version='Unknown' [translation:info] Translating target as defined by src/tinySelf/target [translation] translate.py configuration: [translation] [translate] targetspec = src/tinySelf/target [translation] translation configuration: [translation] [translation] gc = incminimark gctransformer = framework list_comprehension_operations = True withsmallfuncsets = 5 [translation:info] Annotating&simplifying... [33] {translation-task starting annotate [translation:info] with policy: rpython.annotator.policy.AnnotatorPolicy [f5] translation-task} [Timer] Timings: [Timer] annotate --- 6.1 s [Timer] ======================================== [Timer] Total: --- 6.1 s [translation:info] Error: File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/goal/translate.py", line 318, in main drv.proceed(goals) File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/driver.py", line 551, in proceed result = self._execute(goals, task_skip = self._maybe_skip()) File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/tool/taskengine.py", line 114, in _execute res = self._do(goal, taskcallable, *args, **kwds) File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/driver.py", line 278, in _do res = func() File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/driver.py", line 315, in task_annotate s = annotator.build_types(self.entry_point, self.inputtypes) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 92, in build_types return self.build_graph_types(flowgraph, inputs_s, complete_now=complete_now) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 140, in build_graph_types self.complete() File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 229, in complete self.complete_pending_blocks() File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 224, in complete_pending_blocks self.processblock(graph, block) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 398, in processblock self.flowin(graph, block) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 501, in flowin self.consider_op(op) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 653, in consider_op resultcell = op.consider(self) File "/home/bystrousak/Plocha/tests/pypy/rpython/flowspace/operation.py", line 104, in consider return spec(annotator, *self.args) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/unaryop.py", line 118, in simple_call_SomeObject return s_func.call(argspec) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/unaryop.py", line 978, in call return bookkeeper.pbc_call(self, args) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/bookkeeper.py", line 535, in pbc_call s_result = unionof(*results) File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/model.py", line 771, in unionof s1 = pair(s1, s2).union() File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/binaryop.py", line 93, in union raise UnionError(obj1, obj2) [translation:ERROR] UnionError: Offending annotations: SomeInstance(can_be_None=True, classdef=rply.token.BaseBox) SomeTuple(items=(SomeString(const='$end', no_nul=True), SomeList(listdef=<[SomeString(const='$end', no_nul=True)]>))) Occurred processing the following simple_call: function at_the_top_of_the_root_is_just_expression <src/tinySelf/parser.py, line 65> returning function multiple_expressions_make_code <src/tinySelf/parser.py, line 70> returning function self_parser <src/tinySelf/parser.py, line 82> returning function expression_number <src/tinySelf/parser.py, line 88> returning function expression_string <src/tinySelf/parser.py, line 94> returning function expression_strings_numbers <src/tinySelf/parser.py, line 106> returning function unary_message <src/tinySelf/parser.py, line 113> returning function unary_message_to_expression <src/tinySelf/parser.py, line 118> returning function binary_message_to_expression <src/tinySelf/parser.py, line 124> returning function keyword_message <src/tinySelf/parser.py, line 133> returning function keyword_message_to_obj <src/tinySelf/parser.py, line 138> returning function keyword <src/tinySelf/parser.py, line 143> returning function keyword_multiple <src/tinySelf/parser.py, line 148> returning function keyword_message_with_parameters <src/tinySelf/parser.py, line 158> returning function keyword_message_to_self_with_parameters <src/tinySelf/parser.py, line 185> returning function keyword_message_to_obj_with_parameters <src/tinySelf/parser.py, line 212> returning function all_kinds_of_messages_are_message <src/tinySelf/parser.py, line 239> returning function expression_is_message <src/tinySelf/parser.py, line 245> returning function cascade <src/tinySelf/parser.py, line 267> returning function cascades <src/tinySelf/parser.py, line 281> returning function expression_cascade <src/tinySelf/parser.py, line 297> returning function slot_names <src/tinySelf/parser.py, line 316> returning function nil_slot_definition <src/tinySelf/parser.py, line 332> returning function slot_definition <src/tinySelf/parser.py, line 339> returning function slot_definition_rw <src/tinySelf/parser.py, line 362> returning function nil_argument_definition <src/tinySelf/parser.py, line 370> returning function slot_name_kwd_one <src/tinySelf/parser.py, line 378> returning function slot_name_kwd_multiple <src/tinySelf/parser.py, line 383> returning function slot_name_kwd <src/tinySelf/parser.py, line 393> returning value_0 = simple_call(v0, targ_0) In <FunctionGraph of (rply.parser:67)LRParser._reduce_production at 0x6f6e1e0>: Happened at file /home/bystrousak/.local/lib/pypy2.7/site-packages/rply/parser.py line 80 ==> value = p.func(targ) Known variable annotations: v0 = SomePBC(can_be_None=True, descriptions={...29...}, knowntype=function, subset_of=None) targ_0 = SomeList(listdef=<[SomeInstance(can_be_None=False, classdef=rply.token.Token)]mr>) Processing block: block@164[targ_0...] is a <class 'rpython.flowspace.flowcontext.SpamBlock'> in (rply.parser:67)LRParser._reduce_production containing the following operations: v0 = getattr(p_0, ('func')) value_0 = simple_call(v0, targ_0) --end-- [translation] start debugger... > /home/bystrousak/Plocha/tests/pypy/rpython/annotator/binaryop.py(93)union() -> raise UnionError(obj1, obj2)
ze které je relevantní asi tak tohle:
Offending annotations: SomeInstance(can_be_None=True, classdef=rply.token.BaseBox) SomeTuple(items=(SomeString(const='$end', no_nul=True), SomeList(listdef=<[SomeString(const='$end', no_nul=True)]>)))
Nedostanete však číslo řádku, ani jméno funkce kde se to děje (možná to tak vypadá v tom výpisu, ale to jsou irelevantní stack-traces, tu chybu jsem nakonec našel úplně někde jinde zakomentováváním kusů kódu). Vidíte jen že je to nějaký tuple ve kterém jsou stringy a listy. Pěkný článek o tom je tady: The Magic of RPython.
taky jsme jednu věc postupně přepsali a pozor na to, že ten The Magic of Python článek je pro starší verzi RPythonu. Dneska už spoustu umělých omezení odstranili, například to nemusí být staticky typované, to už neplatí. Jen je potřeba v každém control pointu znát typ hodnoty v proměnné
No ty moje hlášky a zkušenosti jsou měsíc staré, vůči vývojové verzi RPythonu (přímo z repa). Takže je možné, že jsou lepší, ale pořád jsou dost příšerné.
například to nemusí být staticky typované, to už neplatí
Počkat, co? Já na to narážel asi tak pořád, což mě nutilo psát "kontejner" třídy poděděné od stejného předka. Jak to myslíš?
No jde o to, že kdysi RPython odvodil typ proměnné z typu první hodnoty, která do ní byla přiřazena. A potom to muselo všude sedět. Ale nedávno (?) se to změnilo a RPython dokáže s typem hýbat, samozřejmě pokud si je pořád jistý (žádné přiřazení různých typů v if-ech atd.). Takže toto bez problémů přeložíš a spustíš, ale v tom dobrém ale starším článku Magic of RPython píše, že to nejde (nešlo):
def entry_point(argv): x = "one" print x x = 2 print x x = None print x x = True print x x = range(10) print x[1] return 0
def target(driver, args): return entry_point, None
Btw. umí někdo spojit možnosti [code] a [pre] tady v diskuzi? Ty krátké ukázky kódu jsou celkem na nic.
Nekde v tomto miste bych se rad diskutujicich odborniku zeptal, jestli jsem tedy velke prase, kdyz behem zivota promenne v ni mivam ruzne typy? Uplne v tom nejbeznejsim priklade rad mivam v promenne bud None nebo nejakou hodnotu. (Na to jsem si zvyknul v C#, kde byly typy napr. bool?, int? atd.) A potom jsem se s necim takovym setkal u nejake knihovny pro nacitani z excelu, kde hodnota bunky byla cislo, nebo string - podle toho, co bylo v excelu.
Jsem myslel, ze tohle se v pythonu muze a bezne dela?
To, ze holt nemuzu pouzivat RPython, asi nejak preziju.
Jde to proti filosofii čistého kódu tak, jak jí definuje kniha Čistý kód. Není to nutně špatně, ale nic podstatného tím neušetříš co do systémových prostředků a hlavně pro lidi to může být matoucí, když jedna proměnná s jedním popisným názvem je použitá v několika úplně odlišných kontextech, nedejbože pro úplně rozdílné datové typy.
Neřekl bych, že jsi velké prase, sám to tak taky občas (!) dělám, ale mohl bys to psát čistěji.
Tu knihu mimochodem vážně doporučuji. Bohužel je rozebraná a nedá se sehnat ani v antikvariátech, a na dotaz jestli nechystají dotisk, že bych si klidně koupil 10 dílů, abych je mohl rozdávat juniorům mi odpověděli, že ne. Ale PDF by se mělo dát sehnat na populárním českém webu pro sdílení souborů.
RPython v soucasne verzi zvlada nullable pro prakticky kazdy typ, takze toto problem neni. Umoznuje i zmenit typ hodnoty prirazene do promenne, ale jen ve chvili, kdy si dokaze ten typ jednoznacne odvodit. Prave proto jsem uvadel ten priklad, kde nema sanci to odvodit - vse zavisi na vecech neznamych v case prekladu (pocet args predavanych pres CLI). Ja se snazim hlavne omezovat dobu zivota promenne na co nejkratsi bloky, ale zase v Pythonu bych se typovanim nestresoval :) pokud nepouzivas MyPy a neresis si to skutecne striktne.
Já to používám běžně. Třeba u proměnných, které ukazují na svým způsobem stejná data, ale v jiné formě. Třeba mám pole, do kterého přidávám stringy (a dejme tomu ze se na nej odkazuji promennou err_message) a na závěr ho přes join spojím a udělám z něj text. Na výsledný text se odkazuji stejnou proměnnou jako na to pole.
Na druhou stranu, protože je to framework pro tvorbu interpretů, tak je obecnější než překladače do C. RPython program může být přeložen do C nebo taky do Java bajtkódu. Je možné experimentovat s různými GC atd.
Takže RPython je trochu takový akademický na druhou stranu tu je už docela dlouho a když se v tom dá implementovat interpret Pythonu (PyPy), mělo by to být použitelné i pro normální inženýrské záležitosti.
Jako, já zas nechci tvrdit, že to použitelné není. Konec konců, i socha se dá sochat šroubovákem místo dláta. Jen jsem chtěl podotknout, že to nemusí být vhodné a že existují vhodnější projekty.
Konkrétně třeba ta Nuitka je docela vyspělá (skoro 5k commitů, minimálně 8 let vývoje, poslední commit měsíc zpět) a nemusíš přepisovat věci do RPythonu, který je místy fakt omezený a úplně v něm zapomeň na standardní knihovnu pythonu, ze které tam je asi tak 10%.
Já se snažil informaci o tom, kdy, kde a proč se RPython používá, říct ve druhé kapitole. Původně jsem totiž plánoval článek o "střevech" PyPy, ale tam je toho tolik, že jsem začal s RPythonem, který se mi - musím přiznat - obecně hodně líbí.
Samozřejmě záleží na řešeném problémy, ale zrovna pro úlohy, kde se něco počítá nebo simuluje, tak (pokud to už není v Numpy) není s překladem v RPythonu problém, protože tyto úlohy typicky používají homogenní pole (eh. seznamy), žádné definice uvnitř funkcí a další hodně dynamické věci, které RPython nepřeloží (a nepřeloží je žádný static compiler).
(jinak autoři PyPy se skutečně na několika místech brání tomu, aby lidi RPython používali. Někde tvrdí, že pokud dá RPython výrazně rychlejší kód, než PyPy, je to chyba v PyPy, která by měla být hlášena. A mají částečně pravdu; na druhou stranu mít možnost z pythonu vygenerovat relativně malou binárku nebo so/DLL taky není na škodu).
Je to zatím v hodně rozpracované fázi (hotový lexer, parser a částečně objektový layout, momentálně dělám na kompilátoru a VM tak nějak zároveň), nemám na to zas tak moc času, kolik bych chtěl:
https://github.com/Bystroushaak/tinySelf
Je to Selfem inspirovaný jazyk (superset syntaxe), který by měl být ve výsledku víc prakticky orientovaný. Inspirací mi je RPySOM a RSqueak, i když samozřejmě majorita vnitřností bude fungovat jinak, kvůli na prototypech založenému objektovému modelu.
Mám o tom všem rozepsané články (hotovy 4 díly z prozatimních sedmi, ale nakonec jich bude pravděpodobně tak 10). Píšu většinou pro abclinuxu, tak to bude nejspíš časem tam. Nechci to publikovat v průběhu, aby z toho nebyl další rozepsaný seriál, na který se autor vykašle, když ho zaujme něco jiného.
Tak jsem to začal vydávat. V následujících týdnech se to bude objevovat na mém blogu. První díl je tady: https://www.abclinuxu.cz/blog/bystroushaak/2019/2/jak-se-pise-programovaci-jazyk-1-motivace
RPython je natolik cool, že se na tom dá udělat i doktorát:-)
K těm benchmarkům, ono se dá hrát s optimalizacemi na úrovni generátoru toho C mezikódu a potom samozřejmě s parametry překladu C mezikódu. GCC vlastně může eliminovat některé neefektivnosti toho generovaného C. Když se to pořádně potuní, tak je rozdíl ve výpočetním výkonu oproti ručně psanému C ještě menší.
Viz grafy na stránkách 149 a 151 v http://paskma.info/marek_paska_phd_thesis.pdf.
Kdy RPython bude vždy ztrácet je správa paměti, protože musí mít nějaký GC (BoehmGC nebo počítání referencí).