Guido van Rossum: Felgyorsítjuk a Pythont!

A nyelv kitalálója, Guido van Rossum a pandémiára hivatkozva (unalmában) jelentkezett a Microsofthoz, ahol szabadon választhatott magának projectet. Így hát úgy döntött, visszatér a gyökerekhez és a Pythonnal és annak felgyorsításával fog foglalkozni egy jelenleg kisebb (3 fős) csapatával:

- a HotPy és HotPy 2 során szerzett tapasztalatokra építenek
- tervezett 5x gyorsulás 4 év alatt (1.5x / év)
- type layout, base object és az eredeti API, ABI nem változik, extrém esetekben sem lehet lasabb az új megvalósítás 
- a compiler, interpreter, bytecode és "most object's internal" változhat
- elképzelhető egy "machine code generation" a jövőben
- "CPU-intensive" python kódok és pythonos weboldalak profitálhatnak leginkább a fejlesztésekből

A változások leghamarabb a jövőre megjelenő, 3.11-es verzióban lesznek elérhetőek, 2x sebességjavulást prognosztizálva.

Részletek a közzétett pdf-ben.

Hozzászólások

Szerkesztve: 2021. 05. 18., k – 14:35

Tök jó. Bár lehet, hogy a PyPy projektbe kellene több energiát beletolni
 

#!/usr/bin/python3

import sys
print (sys.version)

def teszt(num):
    acc = 0
    for i in range(num):
        for j in range(num):
            acc += i^j
    print(acc)

teszt(10*1000)

 

$ time python3 teszt.py
3.9.4 (default, Apr  4 2021, 19:38:44)
[GCC 10.2.1 20210401]
642122061696

real    0m10,555s
user    0m10,552s
sys    0m0,000s



$ time pypy3 teszt.py
3.6.12 (7.3.3+dfsg-3, Feb 26 2021, 04:58:43)
[PyPy 7.3.3 with GCC 10.2.1 20210220]
642122061696

real	0m0,231s
user	0m0,188s
sys	0m0,024s

Épp itt az ideje, a perl már most 2x olyan gyors.

#!/usr/bin/perl

use strict;
use warnings;

print "$^V\n";

sub teszt
{
    my $num = shift;
    --$num;
    my $acc = 0;
    for my $i (0 .. $num) {
        for my $j (0 .. $num) {
            $acc += $i ^ $j;
        }
    }
    print "$acc\n";
}

teszt(10_000);

exit 0;

 

$ time -p ./speed.py
3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110]
642122061696
real 13.69
user 13.68
sys 0.00
$ time -p ./speed.pl
v5.32.1
642122061696
real 6.96
user 6.96
sys 0.00
$ 

Szkriptnyelvek közül a pehelysúlyú, amit az OpenWRT luci webes adminfelülete is használ és sok más programba bele van építve (apache, nginx, nmap, ...), az a LUA.
Ezt mérd meg lua-5.4 futtatókörnyezettel

#!/usr/bin/lua

function teszt(num)
    local acc = 0
    for i=0,num-1 do
        for j=0,num-1 do
            acc = acc + (i~j)
        end
    end
    print(acc)
end

teszt(10*1000)

XOR ~ jelén mondjuk kitéptem a hajam. Miért kellett eltérni?

A LUA 5.2 és régebbi időkben a bit csomagból kellett használni. A luajit még 5.1 szintaktikás, de nagyon fürge.
Ezt is megmérheted. Ilyen jellegű feladatokhoz ritka fürge a szkriptnyelvek között.

#!/usr/bin/luajit

local bit = require "bit"

function teszt(num)
    local acc = 0
    for i=0,num-1 do
        for j=0,num-1 do
            acc = acc + bit.bxor(i, j)
        end
    end
    print(acc)
end

teszt(10*1000)

Hurrá, kíváncsi vagyok mit sikerül elérni (így tippre nem sokat).

Mindenesetre ha mégis, remélem a következő nagy lépés a valódi multithreading irányába fog történni.

Gyakorlatilag a Python egyszalu, mert hulyen irtak meg (jo, ennek is van oka, de roviden ennyi). Letrehozhatsz uj szalakat, de van egy global lock, ami mindig csak egy-egy szalat enged egyszerre futni. Multiprocessinggel persze athidalhato a dolog valamennyire, de akkor meg a processek kozti kommunikacio korlatozza a dolgot.

When you tear out a man's tongue, you are not proving him a liar, you're only telling the world that you fear what he might say. -George R.R. Martin

Hülyén írták meg -> hülyebiztos(abb)ra írták meg

A rosszul kezelt versenyhelyzet rosszabb, mintha nincs versenyhelyzet, mert egzotikus nehezen reprodukálható hibát okoz. Ez itt nincs, cserében tényleg lassabb. Ritkán zavar, lehet több példányt futtatni és akkor példányonként van egy gil, nem akad össze.

Ha felsz a versenyhelyzettol (teny, komplexitast hoz a rendszeredbe), akkor nem muszaj thread-eket hasznalni, es nincs rosszul kezelt versenyhelyzet sem.

Ha azonos adaton kell dolgoznia, nem annyira jo a tobb process, mert az adat ide-oda kuldozgetese felesleges overhead.

Volt olyan projectem, amiben jo lett volna, ha normalis threadek vannak, es az sem zavart volna, ha van egy masik, buta thread implementacio, ami minden mast blokkol.

When you tear out a man's tongue, you are not proving him a liar, you're only telling the world that you fear what he might say. -George R.R. Martin

A multiprocessing pool-ja most is tud map-et meg ennek az aszinkron valtozatat. A problemam nem a kenyelmevel van, hanem az, hogy ez kulon processben fut, es az adatot oda-vissza szerializalnia kell.

Pl. van egy ilyen egyszalu kod, ami egy l lista elemeire (lehet mas is) raereszti az fv fuggvenyt, es visszaadja az eredmenyt:
result=map(fv,l)
Ez tobszalukent arra modosul, hogy:
result=pool.map(fv,l)
Ha elotte letrehoztad a pool-t: pool
=multiprocessing.Pool(4) # ez 4 szalon fog feldolgozni.

Kuldheted hatterbe is, pool.map_async(fv,l,1,done_callback), itt a visszaterese nem erdekes, a done_callback fuggvenyedet hivja meg az eredmennyel, aszinkron modon. Persze ebben az esetben utolag leloheted, ha megsem erdekel mar az eredmenye, mert megy tovabb a kodod, vagy ha gondolod (kene mar az eredmeny) megvarhatod, stb..

When you tear out a man's tongue, you are not proving him a liar, you're only telling the world that you fear what he might say. -George R.R. Martin

Lehet versenyhelyzetet csinálni, csak nem tolja a képedbe, hogy használd és igyekszik kerülni amíg lehet. Ennek jó példája az async io, ami látszik, hogy még mindig idegen elem kicsit a nyelvben, de pl pont arra ad alternatívát, amikor először elérsz a falig és eszedbe jutna thread szaporítás.

Nincs bajom a versenyhelyzet kezelésével. De azzal igen, hogy nehezen derül ki élesítés előtt, hogy egyáltalán van ilyen hiba a rendszerben.

Hint: ne az egyszemélyes hobbi projektre gondolj, ahol tudod, hogy mennyire dolgozol minőségben, vagy hol hagytál pár kompromisszumot. Hanem amikor nem tudod, hogy a kollégák hol hagytak... Tudom, code review. 

"hülyebiztos(abb)ra írták meg"

Sajnos nem igaz, a GIL egyszerűen lecseréli a versenyhelyzeteket deadlockokra. Csöbörből vödörbe. Ráadásul emiatt a natív OS hívások (pl socket, file IO) sem blokkolhatnak, ami egy külön agyrém. Trust me, debuggoltam ilyen hibákat nagy python projektekben (értsd: openstack subproject). A legszörnyűbb, hogy sokszor nem is maga a GIL, hanem a GIL workaroundolására kitalált eventlet, green threads meg hasonló library-k idéznek elő teljesen elképesztő hibákat.

Régóta vágyok én, az androidok mezonkincsére már!

Ezt csak úgy ide bedobom a GIL-less Pythonról: https://lukasz.langa.pl/5d044f91-49c1-4170-aed1-62b6763e6ad0/

Egyébként van olyan Python implementáció (Jython, GraalPython) amikben nincs GIL. (PyPy-ban van).

Egy másik érdekes irány szerintem a mypyc: https://mypyc.readthedocs.io/en/latest/index.html

Ugyanitt: "Trabant igényeseknek extrákkal eladó"

 Érdekességképpen nim script vs python script a legvégén nim compiled bináris vs pypy3 python. :-)

[code@x3363 NIM]$ cat first.nims
#!/usr/bin/env -S nim --hints:off --maxLoopIterationsVM:90000000 --threads=on --opt=speed
# this shebang runs code as interpreted (nim)script
var sum: int
var i: int
sum = 0
i = 0
while i < 10_000_000:
  inc(i, 1)
  inc(sum, i)

[code@x3363 NIM]$ cat first.py
sum = 0
i = 0
while i < 10000000:
  i += 1
  sum += i

[code@x3363 NIM]$ time ./first.nims #nim-script mode

real	0m15,443s
user	0m15,390s
sys	0m0,029s
[code@x3363 NIM]$ time python3 ./first.py

real	0m2,518s
user	0m2,505s
sys	0m0,012s
[code@x3363 NIM]$ time pypy3 first.py 

real	0m0,099s
user	0m0,059s
sys	0m0,027s

[code@x3363 NIM]$ nim c -d:release --opt=speed --threads=on first.nims
................................................................
CC: stdlib_digitsutils.nim
CC: stdlib_assertions.nim
CC: stdlib_dollars.nim
CC: stdlib_system.nim
CC: first.nim
37977 lines; 2.601s; 39.422MiB peakmem; proj: /home/code/Projects/NIM/first.nims; out: /home/code/Projects/NIM/first [SuccessX]
[code@x3363 NIM]$ time ./first

real	0m0,009s
user	0m0,008s
sys	0m0,001s
[code@x3363 NIM]$ 

Ha valaki még kíváncsi a Talk Python to me podcast két november eleji adásában pont ezekről volt szó:

https://talkpython.fm/episodes/show/339/making-python-faster-with-guido-and-mark

https://talkpython.fm/episodes/show/340/time-to-jit-your-python-with-py…

Az utóbbihoz kapcsolódva, láttam fentebb páran észre vették, hogy a Pypy - ami egy jit compiler - gyorsabban hajtja végre a Python kódot mint a CPython - ami egy Ahead of time compiler - cserébe a nagyobb memória használatért meg, hogy nem lehet C-ben írt modulokat használni. Ha valakinek ez elfogadható kompromisszum akkor csak tessék. Egyébként meg létezik már Pyjion. Ez a .net runtime-ot használja, habár több szálúságot még ez sem tud.

A CPython sose volt AOT fordító, teljesen interpreter alapú. Van C API-ja, amivel lehet Python Extensionoket illeszteni hozzá bármilyen C API kompatibilis nyelvből, azok valóban általában előre le vannak fordítva, de a Pythonban írt kód interpretálva fut.

Python AOT-re van több próbálkozás is, pl. a Cython, ami nyelvi kiterjesztéseket is hoz, és a Mypyc (amit fentebb linkeltem), ami a szabványos Python type hinteket felhasználva próbál hatékony natív kódot generálni.

.NET-re épülő Python implementáció már ~15 éve létezik, úgy hívják, hogy IronPython. Támogat többszálúságot, a Python kód CIL-re fordul és onnan a .NET JIT futtatja, nincs benne GIL. Sajnos az utóbbi időben kicsit lemaradt nyelvi támogatás szempontjából a CPythonhoz képest (most jelent meg nemrég a 3.4 alpha). Hasonló történt a Jython-nal is, ami ugyanez Java platformra. Itt a GraalPython tűnik érdekes projektnek, de sajnos lassabban halad, mint ideális lenne.

A Pyjion-t nem ismertem, de a projekt leírása alapján ez a CPythonhoz csinál JIT támogatást biztosító API-t, aminek az első implementációja CoreCLR alapú, de a projekt célja, hogy mást is lehessen illeszteni hozzá. A projekt README-ben egyébként elég jól összeszedték, hogy miben különbözik az ismertebb alternatív implementációktól: https://github.com/tonybaloney/Pyjion#how-do-this-compare-to