Hlavní navigace

Nuitka: pokročilý AOT překladač jazyka Python

23. 5. 2024
Doba čtení: 24 minut

Sdílet

 Autor: Root.cz s využitím DALL-E
Na články o AOT překladačích (mypyc a Cython) i o JIT překladačích (Numba) dnes navážeme a celé téma dokončíme. Popíšeme si totiž některé možnosti nabízené AOT překladačem nazvaným Nuitka.

Obsah

1. Nuitka: pokročilý AOT překladač jazyka Python

2. Instalace balíčku Nuitka

3. Kontrola instalace

4. Překlad jednoduchého skriptu typu „Hello world“

5. Prozkoumání výsledku překladu

6. Překlad a slinkování za účelem přenosu aplikace na jiný počítač

7. Výsledný spustitelný soubor a jeho závislosti

8. Překlad se slinkováním potřebných knihoven do jediného souboru

9. Aplikace využívající knihovnu Tkinter pro grafické uživatelské rozhraní

10. Překlad projektu tak, aby výsledkem byl jediný spustitelný soubor

11. Výsledný spustitelný soubor a jeho závislosti

12. Porovnání kvality překladu pomocí nástroje Nuitka s dalšími technologiemi

13. Porovnávané technologie

14. Zdrojové kódy benchmarků

15. Výsledky benchmarků v tabulkové podobě

16. Vizualizace výsledků benchmarků

17. Porovnání výsledků benchmarků: interpretry

18. Porovnání výsledků benchmarků: překladače

19. Příloha: skript pro vytištění grafů s výsledky benchmarků

20. Odkazy na Internetu

1. Nuitka: pokročilý AOT překladač jazyka Python

Na stránkách Roota jsme se již setkali s několika AOT (Ahead of Time) překladači jazyka Python. Tyto překladače většinou transformují (transpilují) skript či celý projekt napsaný v Pythonu do jazyka C a poté ho nechají přeložit překladačem céčka, typicky s povolenými optimalizacemi. Připomeňme si, že mezi tyto projekty patří Cython (ostatně před pár dny jsme si ho připomenuli) a mypyc (ovšem částečně i Numba). Dnes se zaměříme na projekt, který se jmenuje Nuitka. Opět se jedná o AOT překladač a jeho výhodou je, že v případě potřeby dokáže celou aplikaci přeložit do jediného spustitelného souboru, který je přenositelný na další počítače (které ani nemusí obsahovat Python a dokonce například ani některé knihovny pro GUI atd.).

Obrázek 1: Logo projektu Nuitka.

Na závěr si tento projekt porovnáme se všemi ostatními AOT i JIT překladači Pythonu, a to opět na mém oblíbeném benchmarku založeném na výpočtu Mandelbrotovy množiny. Zjistíme tak, kdy může být výhodnější použít JIT typu Numba a kdy AOT překladač.

2. Instalace balíčku Nuitka

Balíček Nuitka je, ostatně podobně jako většina užitečných balíčků pro jazyk Python, dostupná na PyPi, takže je jeho instalace velmi snadná. Pro jednoduchost použiji nástroj pip, i když lze pochopitelně použít i PDM či další podobné nástroje:

$ pip3 install nuitka

Průběh instalace (povšimněte si, že Nuitka je relativně malý balíček):

Defaulting to user installation because normal site-packages is not writeable
Collecting nuitka
  Downloading Nuitka-2.2.3.tar.gz (3.7 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.7/3.7 MB 4.3 MB/s eta 0:00:00
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Requirement already satisfied: ordered-set>=4.1.0 in /home/ptisnovs/.local/lib/python3.11/site-packages (from nuitka) (4.1.0)
Requirement already satisfied: zstandard>=0.15 in /home/ptisnovs/.local/lib/python3.11/site-packages (from nuitka) (0.22.0)
Building wheels for collected packages: nuitka
  Building wheel for nuitka (pyproject.toml) ... done
  Created wheel for nuitka: filename=Nuitka-2.2.3-cp311-cp311-linux_x86_64.whl size=3277344 sha256=dd58819d552c3cfd2e744848cd5a9676b2820a36d3d202560bc7ef0e8f41ac6d
  Stored in directory: /home/ptisnovs/.cache/pip/wheels/d8/48/83/b339d592b2bf8ae0d73c8fe6dd14dca269b691157696c3d7bc
Successfully built nuitka
Installing collected packages: nuitka
Successfully installed nuitka-2.2.3

3. Kontrola instalace

Pro rychlé otestování, zda je balíček nainstalován korektně, postačuje spustit modul nuitka, například s parametrem –version:

$ python -m nuitka --version
2.2.3
Commercial: None
Python: 3.11.8 (main, Feb  7 2024, 00:00:00) [GCC 13.2.1 20231011 (Red Hat 13.2.1-4)]
Flavor: Fedora Python
Executable: /usr/bin/python
OS: Linux
Arch: x86_64
Distribution: Fedora 38
Version C compiler: /usr/bin/gcc (gcc 13).
Poznámka: povšimněte si, že se vypsal i překladač céčka, který bude interně používán.

4. Překlad jednoduchého skriptu typu „Hello world“

Praktickou část dnešního článku zahájíme triviálním skriptem typu „Hello world“, který může v Pythonu vypadat následovně:

def say_hello():
    print("Hello world!")
 
say_hello()

Základní způsob překladu tohoto skriptu (tedy vlastně „projektu“) do spustitelného nativního kódu se provede takto:

$ python -m nuitka hello.py

Následně proběhne překlad, který bude trvat několik sekund (na starším HW pravděpodobně i desítek sekund!):

Nuitka-Options: Used command line options: hello.py
Nuitka-Options:WARNING: You did not specify to follow or include anything but main program. Check options and make sure that is
Nuitka-Options:WARNING: intended.
Nuitka: Starting Python compilation with Nuitka '2.2.3' on Python '3.11' commercial grade 'not installed'.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 13).
Nuitka-Scons: Backend linking program with 6 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package
Nuitka-Scons:WARNING: manager to install it.
Nuitka: Keeping build directory 'hello.build'.
Nuitka: Successfully created 'hello.bin'.

Výsledkem je spustitelný (nativní) program, který si skutečně spustíme:

$ ./hello.bin

Výsledkem by měla být pochopitelně tato zpráva:

Hello world!
Poznámka: překlad skutečně není nejrychlejší, což si můžeme ověřit například utilitkou time:
$ python -m nuitka hello.py

Výsledky změřené na stroji s procesorem i7–1270P (počet jader nehraje zásadní roli) ukazují, že i pro několikařádkový skript si chvíli počkáme:

real    0m3.062s
user    0m5.044s
sys     0m0.332s

5. Prozkoumání výsledku překladu

Prozkoumejme ještě blíže soubor hello.bin, který byl vytvořen překladačem Nuitka. Nejprve si ověříme, že se skutečně jedná o spustitelný soubor s nativním kódem:

$ file hello.bin
 
hello.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=96c80b076b56dbdf3da519fdbc564294288a6b8d, for GNU/Linux 3.2.0, stripped

Velikost tohoto souboru není na dnešní poměry nijak závratná (i když například FASM přeloží sémanticky stejný program do 231 bajtů):

$ ls -l hello.bin
 
-rwxr-xr-x. 1 ptisnovs ptisnovs 251216 May 20 13:46 hello.bin

Ovšem velikost spustitelného souboru není zdaleka vše, protože ještě záleží, jaké knihovny se musí načíst v průběhu inicializace. Takže si zjistěme, o jaké knihovny se jedná:

$ ldd hello.bin

Seznam dynamicky linkovaných knihoven:

        linux-vdso.so.1 (0x00007ffe87df4000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f763cafb000)
        libpython3.11.so.1.0 => /lib64/libpython3.11.so.1.0 (0x00007f763c400000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f763c222000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f763cbf2000)

Zde již není vše tak růžové, jak by se mohlo zdát, protože je zapotřebí celá runtime knihovna libpython3.11.so, a ta již objemná je:

$ ls -lh /lib64/libpython3.11.so.1.0
 
-rwxr-xr-x. 1 root root 5.2M Feb  7 01:00 /lib64/libpython3.11.so.1.0

6. Překlad a slinkování za účelem přenosu aplikace na jiný počítač

Překladač Nuitka dokáže překlad provést i takovým způsobem, že se vytvoří adresář se všemi potřebnými soubory, které lze v případě potřeby následně přenést na jiný počítač, na němž ani nemusí být nainstalován Python – takže lze Nuitku použít i pro tvorbu instalačních balíčků. Dokonce existuje několik způsobů, jak takový překlad provést. Ukažme si nejprve použití přepínače –standalone:

$ python -m nuitka --standalone hello.py

Překlad nyní bude trvat poněkud delší dobu:

Nuitka-Options: Used command line options: --standalone hello.py
Nuitka: Starting Python compilation with Nuitka '2.2.3' on Python '3.11' commercial grade 'not installed'.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 13).
Nuitka-Scons: Backend linking program with 7 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package
Nuitka-Scons:WARNING: manager to install it.
Nuitka: Keeping build directory 'hello.build'.
Nuitka: Successfully created 'hello.dist/hello.bin'.

Rychlost překladu si můžeme opět ověřit utilitkou time. Povšimněte si, že se skutečně doba překladu (a slinkování) poměrně razantně zvýšila:

real    0m12.592s
user    0m12.609s
sys     0m0.477s

Výsledkem nyní ovšem není pouze jeden spustitelný soubor, ale celý adresář určený pro přenos/instalaci na další počítač:

binascii.so
_codecs_cn.so
_codecs_hk.so
_codecs_iso2022.so
_codecs_jp.so
_codecs_kr.so
_codecs_tw.so
_datetime.so
fcntl.so
hello.bin
libpython3.11.so.1.0
math.so
_multibytecodec.so
_pickle.so
_random.so
_sha512.so
_struct.so
_typing.so
unicodedata.so
zlib.so

7. Výsledný spustitelný soubor a jeho závislosti

Spustitelný soubor vzniklý překladem je již mnohem větší:

$ ls -lh hello.bin
 
-rwxr-xr-x. 1 ptisnovs ptisnovs 5.4M May 21 19:23 hello.bin

Nicméně stále je vyžadována knihovna libpython3.11.so, tentokrát ovšem načítaná nikoli ze systémové cesty, ale z místa, kde byl vytvořen adresář s instalačními soubory:

$ ldd hello.bin
        linux-vdso.so.1 (0x00007ffdba5e1000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f8fb1064000)
        libpython3.11.so.1.0 => /tmp/ramdisk/hello.dist/./libpython3.11.so.1.0 (0x00007f8fb0b02000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f8fb0924000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8fb115b000)

8. Překlad se slinkováním potřebných knihoven do jediného souboru

Mnohem užitečnější (pro potřeby instalace) je však přepínač –onefile, který provádí operaci odpovídající jeho jménu. Vytvoří tedy spustitelný soubor, který závisí jen na systémových dynamicky linkovaných knihovnách, ale nikoli na knihovnách Pythonu:

$ python -m nuitka --onefile hello.py

Opět si počkáme, v mém konkrétním případě cca 17 sekund (to je již dost):

Nuitka-Options: Used command line options: --onefile hello.py
Nuitka: Starting Python compilation with Nuitka '2.2.3' on Python '3.11' commercial grade 'not installed'.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 13).
Nuitka-Scons: Backend linking program with 7 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package
Nuitka-Scons:WARNING: manager to install it.
Nuitka-Postprocessing: Creating single file from dist folder, this may take a while.
Nuitka-Onefile: Running bootstrap binary compilation via Scons.
Nuitka-Scons: Onefile C compiler: gcc (gcc 13).
Nuitka-Scons: Onefile linking program with 1 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package
Nuitka-Scons:WARNING: manager to install it.
Nuitka-Onefile: Using compression for onefile payload.
Nuitka-Onefile: Onefile payload compression ratio (28.09%) size 13711475 to 3851813.
Nuitka-Onefile: Keeping onefile build directory 'hello.onefile-build'.
Nuitka: Keeping dist folder 'hello.dist' for inspection, no need to use it.
Nuitka: Keeping build directory 'hello.build'.
Nuitka: Successfully created 'hello.bin'.

Čas:

real    0m17.945s
user    0m17.655s
sys     0m0.811s

Velikost výsledného souboru nyní dosahuje přibližně 4MB:

$ ls -lh hello.bin
 
-rwxr-xr-x. 1 ptisnovs ptisnovs 3.9M May 21 19:32 hello.bin

A nyní je program spustitelný a závislý pouze na základních systémových knihovnách:

$ ldd hello.bin
 
        linux-vdso.so.1 (0x00007ffd1dbe5000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f8a23d29000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8a23f1d000)
Poznámka: dosáhli jsme tedy přibližně stejné funkcionality, kterou nám nabízí například překladače jazyků Go či Rust. Výsledný spustitelný soubor lze využít v kontejneru atd. atd.

9. Aplikace využívající knihovnu Tkinter pro grafické uživatelské rozhraní

V dalším kroku si vyzkoušíme překlad nepatrně složitějšího projektu, který ale ukáže, jak Nuitka pracuje se závislými knihovnami. Bude se jednat o projekt založený na knihovně Tkinter a budeme vyžadovat, aby výsledný spustitelný soubor byl přenositelný i na počítač bez Pythonu, Tk i Tkinteru.

Obrázek 2: Takto vypadá okno po spuštění našeho demonstračního projektu.

Zdrojový kód našeho projektu (či spíše „projektu“) vypadá následovně:

import tkinter
from tkinter import ttk
import sys
 
 
WIDTH = 400
HEIGHT = 400
GRID_SIZE = 100
 
 
def exit():
    sys.exit(0)
 
 
def basic_canvas(root, width, height, grid_size):
    canvas = tkinter.Canvas(root, width=width, height=height, background="#ccffcc")
    canvas.pack()
 
    draw_grid(canvas, width, height, grid_size)
    return canvas
 
 
def draw_grid(canvas, width, height, grid_size):
    for x in range(0, width, grid_size):
        canvas.create_line(x, 0, x, height, dash=7, fill="gray")
    for y in range(0, height, grid_size):
        canvas.create_line(0, y, width, y, dash=7, fill="gray")
 
 
root = tkinter.Tk()
 
canvas = basic_canvas(root, WIDTH, HEIGHT, GRID_SIZE)
 
canvas.create_line(0, 0, 100, 100, fill="red", width=2, dash=8)
 
canvas.create_arc(
    100, 1, 200, 100, outline="blue", start=45, extent=180, style=tkinter.ARC, width=2
)
 
canvas.create_oval(200, 1, 300, 100)
 
canvas.create_oval(325, 25, 375, 75, fill="#a0a0ff")
 
canvas.create_rectangle(50, 125, 150, 175, fill="#a0a0ff")
 
canvas.create_text(300, 150, text="Hello world!", font="Helvetica 20")
 
canvas.create_polygon(50, 225, 200, 300, 50, 375, fill="#80ff80")
 
canvas.create_polygon(
    250, 225, 400, 300, 250, 375, fill="black", outline="red", width="5"
)
 
root.mainloop()

10. Překlad projektu tak, aby výsledkem byl jediný spustitelný soubor

Pokusme se nyní výše uvedený projekt přeložit takovým způsobem, aby výsledkem byl jediný spustitelný (a přenositelný i instalovatelný) soubor, který nebude vyžadovat další nesystémové knihovny. Opět použijeme přepínač –onefile, který již známe:

$ python -m nuitka --onefile objects_on_canvas.py

Nyní ovšem překlad skončí s varováním, že knihovna Tkinter (ta se stará o grafické uživatelské rozhraní, včetně zpracování událostí atd.) závisí na ekosystému jazyka TCL a aby byl do výsledku zahrnut i tento jazyk, musíme použít ještě jednu volbu –enable-plugin=tk-inter:

Nuitka-Options: Used command line options: --onefile objects_on_canvas.py
Nuitka: Starting Python compilation with Nuitka '2.2.3' on Python '3.11' commercial grade 'not installed'.
Nuitka-Plugins:WARNING: Use '--enable-plugin=tk-inter' for: Tkinter needs TCL included.

Proto tedy navrhovanou volbu použijeme:

$ python -m nuitka --onefile --enable-plugin=tk-inter objects_on_canvas.py

Průběh překladu (nyní si skutečně musíme počkat delší dobu):

Nuitka-Options: Used command line options: --onefile --enable-plugin=tk-inter objects_on_canvas.py
Nuitka: Starting Python compilation with Nuitka '2.2.3' on Python '3.11' commercial grade 'not installed'.
Nuitka-Plugins:anti-bloat: Not including '_bisect' automatically in order to avoid bloat, but this may cause: may slow down by using
Nuitka-Plugins:anti-bloat: fallback implementation.
Nuitka-Plugins:anti-bloat: Not including 'socket' automatically in order to avoid bloat, but this may cause: can break calls of
Nuitka-Plugins:anti-bloat: 'email.utils.make_msgid()'.
Nuitka: Completed Python level compilation and optimization.
Nuitka: Generating source code for C backend compiler.
Nuitka: Running data composer tool for optimal constant value handling.
Nuitka: Running C compilation via Scons.
Nuitka-Scons: Backend C compiler: gcc (gcc 13).
Nuitka-Scons: Backend linking program with 8 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package
Nuitka-Scons:WARNING: manager to install it.
Nuitka-Plugins:tk-inter: Included 85 data files due to Tk needed for tkinter usage.
Nuitka-Plugins:tk-inter: Included 222 data files due to Tcl needed for tkinter usage.
Nuitka-Postprocessing: Creating single file from dist folder, this may take a while.
Nuitka-Onefile: Running bootstrap binary compilation via Scons.
Nuitka-Scons: Onefile C compiler: gcc (gcc 13).
Nuitka-Scons: Onefile linking program with 1 files (no progress information available for this stage).
Nuitka-Scons:WARNING: You are not using ccache, re-compilation of identical code will be slower than necessary. Use your OS package
Nuitka-Scons:WARNING: manager to install it.
Nuitka-Onefile: Using compression for onefile payload.
Nuitka-Onefile: Onefile payload compression ratio (30.61%) size 36614803 to 11208588.
Nuitka-Onefile: Keeping onefile build directory 'objects_on_canvas.onefile-build'.
Nuitka: Keeping dist folder 'objects_on_canvas.dist' for inspection, no need to use it.
Nuitka: Keeping build directory 'objects_on_canvas.build'.
Nuitka: Successfully created 'objects_on_canvas.bin'.
Poznámka: čas překladu nyní dosahuje už skoro půl minuty:
real    0m27.803s
user    0m27.476s
sys     0m1.111s

11. Výsledný spustitelný soubor a jeho závislosti

Opět se podívejme na to, jaké knihovny jsou nutné pro spuštění aplikace s grafickým uživatelským rozhraním. Nyní se jedná pouze o základní systémové knihovny, protože všechny ostatní knihovny jsou součástí výsledného „distribučního souboru“:

$ ldd objects_on_canvas.bin
        linux-vdso.so.1 (0x00007ffd20b98000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f0472b3a000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f0472d2e000)

Zajímat nás bude taktéž celková velikost výsledného souboru vytvořeného nástrojem Nuitka:

$ ls -la objects_on_canvas.bin

Jedná se o cca 11MB velký soubor:

rwxr-xr-x. 1 ptisnovs ptisnovs 11M May 21 19:45 objects_on_canvas.bin
Poznámka: 11 MB sice není málo, ale musíme si uvědomit, že aplikace již nevyžaduje žádné další nestandardní knihovny i to, že podobně koncipované technologie (Go, Rust, CC se statickým slinkováním) produkují podobně velké soubory nebo i soubory mnohem větší – to je daň, kterou v dnešním IT platíme za rozpolcenost standardů a systémů.

Mimochodem, aplikace používá tyto slinkované knihovny:

libb2.so.1
libbrotlicommon.so.1
libbrotlidec.so.1
libbz2.so.1
libcrypto.so.3
libfontconfig.so.1
libfreetype.so.6
libgcc_s.so.1
libglib-2.0.so.0
libgomp.so.1
libgraphite2.so.3
libharfbuzz.so.0
liblzma.so.5
libmpdec.so.3
libpcre2-8.so.0
libpng16.so.16
libpython3.11.so.1.0
libtcl8.6.so
libtk8.6.so
libX11.so.6
libXau.so.6
libxcb.so.1
libXft.so.2
libxml2.so.2
libXrender.so.1

12. Porovnání kvality překladu pomocí nástroje Nuitka s dalšími technologiemi

Ve druhé části dnešního článku si porovnáme kvalitu překladu nástrojem Nuitka s dalšími technologiemi, které jsme si již popsali. Konkrétně budeme měřit dobu výpočtu Mandelbrotovy množiny pro postupně se zvyšující rozlišení výsledného obrázku. Připomeňme si, že tento výpočet obsahuje vnořené smyčky a podmíněné bloky, pracuje s poli (ty jsou simulovány seznamem či n-ticí) a výpočty jsou prováděny s typem float. Jedná se tedy o program, na kterém se mohou optimalizační algoritmy dobře „vyřádit“.

Obrázek 3: Výsledek benchmarků by měl být totožný s tímto obrázkem.

13. Porovnávané technologie

V benchmarcích budeme porovnávat tyto technologie:

  • Výpočet realizovaný céčkovským kódem
    • Bez optimalizací (-O0)
    • S optimalizacemi (-Ofast)
  • Výpočet realizovaný standardním interpretrem Pythonu
    • Python 3.8
    • Python 3.9
    • Python 3.10
    • Python 3.11 (zde byly provedeny interní optimalizace)
    • Python 3.12 (teoreticky by měl být nejrychlejší)
  • Výpočet realizovaný kódem po AOT překladu pomocí mypyc
    • Bez typových informací
    • S typovými informacemi
  • Výpočet realizovaný AOT překladačem Nuitka
  • Výpočet realizovaný AOT překladačem CPython
    • Základní varianta (v podstatě původní Pythonovský kód)
    • S typovými informacemi
    • S optimalizacemi + použitím nogil
  • Výpočet realizovaný JIT překladačem Numba
    • Základní varianta (v podstatě původní Pythonovský kód)
    • S uvedením dekorátoru @jit
    • S optimalizacemi + použitím nativní funkce print

14. Zdrojové kódy benchmarků

# Benchmark Stručný popis Adresa
1 mandelbrot-v1 benchmark, v němž se nepoužívají anotace projektu Numba (čistý CPython) https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v1/
       
2 mandelbrot-v2 použití anotace @jit (Numba) ve funkci, v níž se provádí mnoho výpočtů https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v2/
3 mandelbrot-v3 volání zjednodušených variant funkce print (Numba) https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v3/
4 mandelbrot-v4 použití anotace @jit s parametrem nopython https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v4/
       
5 mandelbrot-v5 varianta benchmarku určená pro překlad s využitím mypyc https://github.com/tisnik/most-popular-python-libs/blob/master/mypyc/man­delbrot5.py
6 mandelbrot-v6 přidání typových informací využitelných AOT překladačem mypyc https://github.com/tisnik/most-popular-python-libs/blob/master/mypyc/man­delbrot6.py
       
7 mandelbrot.c varianta benchmarku naprogramovaná v ANSI C https://github.com/tisnik/most-popular-python-libs/blob/master/mypyc/mandelbrot.c
       
8 mandelbrot/v1_python benchmark s výpočtem Mandelbrotovy množiny, původní varianta naprogramovaná v čistém Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/man­delbrot/v1_python
9 mandelbrot/v2_cython přidání typových informací do funkce calc_mandelbrot, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/man­delbrot/v2_cython
10 mandelbrot/v2_python přidání typových informací do funkce calc_mandelbrot, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/man­delbrot/v2_python

15. Výsledky benchmarků v tabulkové podobě

V případě, že preferujete studovat výsledky benchmarků v tabulkové podobě, zde jsou:

Varianta naprogramovaná v jazyku C bez optimalizací

Rozlišení Čas [s] Paměť [kB]
16×16 0.00 1408
24×24 0.00 1408
32×32 0.00 1408
48×48 0.00 1152
64×64 0.00 1152
96×96 0.00 1280
128×128 0.01 1280
192×192 0.01 1280
256×256 0.01 1280
384×384 0.04 1280
512×512 0.07 1280
768×768 0.16 1152
1024×1024 0.27 1152
1536×1536 0.61 1152
2048×2048 1.08 1280
3072×3072 2.45 1280
4096×4096 4.37 1152

Varianta naprogramovaná v jazyku C s optimalizacemi

Rozlišení Čas [s] Paměť [kB]
16×16 0.00 1152
24×24 0.00 1408
32×32 0.00 1408
48×48 0.00 1408
64×64 0.00 1280
96×96 0.00 1152
128×128 0.00 1152
192×192 0.00 1408
256×256 0.01 1280
384×384 0.03 1280
512×512 0.04 1152
768×768 0.10 1152
1024×1024 0.17 1280
1536×1536 0.38 1280
2048×2048 0.69 1280
3072×3072 1.53 1280
4096×4096 2.73 1280

Základní varianta v Pythonu, použit standardní interpret verze 3.8

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 8320
24×24 0.00 7296
32×32 0.01 7424
48×48 0.01 7296
64×64 0.03 7296
96×96 0.05 7424
128×128 0.08 7296
192×192 0.18 7424
256×256 0.33 7424
384×384 0.74 7296
512×512 1.32 7296
768×768 3.01 7424
1024×1024 6.24 7296
1536×1536 17.85 7296
2048×2048 31.51 7168
3072×3072 71.13 7296
4096×4096 128.46 7168

Základní varianta v Pythonu, použit standardní interpret verze 3.9

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 8576
24×24 0.00 7424
32×32 0.01 7424
48×48 0.01 7424
64×64 0.02 7424
96×96 0.04 7424
128×128 0.08 7424
192×192 0.17 7424
256×256 0.31 7424
384×384 0.69 7552
512×512 1.24 7168
768×768 2.83 7424
1024×1024 5.86 7424
1536×1536 18.28 7424
2048×2048 32.98 7424
3072×3072 71.25 7424
4096×4096 133.52 7424

Základní varianta v Pythonu, použit standardní interpret verze 3.10

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 8448
24×24 0.00 7424
32×32 0.01 7552
48×48 0.01 7424
64×64 0.02 7552
96×96 0.05 7424
128×128 0.08 7424
192×192 0.19 7552
256×256 0.33 7424
384×384 0.76 7552
512×512 1.36 7296
768×768 3.12 7424
1024×1024 6.35 7424
1536×1536 19.68 7552
2048×2048 34.05 7552
3072×3072 77.51 7552
4096×4096 142.95 7424

Základní varianta v Pythonu, použit standardní interpret verze 3.11

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 10240
24×24 0.01 9472
32×32 0.01 9472
48×48 0.01 9216
64×64 0.02 9472
96×96 0.04 9472
128×128 0.06 9472
192×192 0.13 9472
256×256 0.23 9344
384×384 0.51 9344
512×512 0.89 9472
768×768 2.01 9472
1024×1024 3.61 9472
1536×1536 12.80 9472
2048×2048 22.79 9472
3072×3072 51.13 9472
4096×4096 90.92 9344

Základní varianta v Pythonu, použit standardní interpret verze 3.12

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 8960
24×24 0.01 8960
32×32 0.01 9216
48×48 0.01 9088
64×64 0.02 9088
96×96 0.04 8960
128×128 0.07 9088
192×192 0.13 9216
256×256 0.28 9088
384×384 0.62 9216
512×512 1.10 9216
768×768 2.48 9216
1024×1024 4.38 9216
1536×1536 13.49 9088
2048×2048 23.79 9088
3072×3072 53.36 9088
4096×4096 94.92 9088

JIT překladač Numba bez dekorátoru @jit

Rozlišení Čas [s] Paměť [kB]
16×16 0.72 158292
24×24 0.68 158752
32×32 0.70 158656
48×48 0.70 158704
64×64 0.74 158644
96×96 0.79 158428
128×128 1.30 158424
192×192 1.74 158664
256×256 2.15 158532
384×384 3.38 158552
512×512 4.99 158792
768×768 9.94 158160
1024×1024 16.12 157904
1536×1536 35.90 158452
2048×2048 61.63 158504
3072×3072 141.17 158800
4096×4096 249.37 158524

JIT překladač Numba s použitím jednodušší varianty funkce print

Rozlišení Čas [s] Paměť [kB]
16×16 3.04 266960
24×24 3.04 266844
32×32 3.06 267092
48×48 4.06 267108
64×64 4.67 267132
96×96 4.62 267220
128×128 4.58 267172
192×192 4.58 267036
256×256 4.58 267220
384×384 4.70 267084
512×512 4.74 267208
768×768 5.00 267080
1024×1024 5.31 266884
1536×1536 6.23 267228
2048×2048 7.58 267092
3072×3072 11.51 267096
4096×4096 17.19 267184

JIT překladač Numba s dekorátorem @jit(nopython=True)

Rozlišení Čas [s] Paměť [kB]
16×16 3.03 266836
24×24 3.02 267100
32×32 3.95 267172
48×48 4.61 267092
64×64 4.59 267088
96×96 4.95 267220
128×128 4.65 267084
192×192 4.61 267088
256×256 4.67 267220
384×384 4.64 267088
512×512 4.73 267052
768×768 4.97 267088
1024×1024 5.29 267080
1536×1536 6.31 267216
2048×2048 7.63 267076
3072×3072 11.43 267220
4096×4096 16.89 267104

AOT překladač Mypyc, varianta bez typových informací

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 10624
24×24 0.01 9472
32×32 0.01 9472
48×48 0.02 9472
64×64 0.02 9472
96×96 0.05 9472
128×128 0.08 9472
192×192 0.17 9472
256×256 0.29 9472
384×384 0.66 9472
512×512 1.19 9472
768×768 2.87 9472
1024×1024 7.27 9472
1536×1536 16.00 9472
2048×2048 28.36 9344
3072×3072 64.32 9472
4096×4096 116.64 9472

AOT překladač Mypyc, varianta s typovými informacemi

Rozlišení Čas [s] Paměť [kB]
16×16 0.02 10488
24×24 0.01 10356
32×32 0.01 10228
48×48 0.02 10224
64×64 0.02 10352
96×96 0.04 10352
128×128 0.07 10360
192×192 0.13 10232
256×256 0.23 10232
384×384 0.50 10364
512×512 0.91 10360
768×768 2.11 10228
1024×1024 3.83 10228
1536×1536 13.65 10360
2048×2048 24.07 10232
3072×3072 55.30 10356
4096×4096 93.65 10356

AOT překladač Nuitka

Poznámka: stejné výsledky bez i s optimalizacemi.
Rozlišení Čas [s] Paměť [kB]
16×16 0.02 11776
24×24 0.02 11904
32×32 0.02 11904
48×48 0.02 11904
64×64 0.03 12032
96×96 0.04 11904
128×128 0.07 11776
192×192 0.12 11776
256×256 0.21 11904
384×384 0.48 12032
512×512 0.81 11776
768×768 1.87 11904
1024×1024 3.47 11904
1536×1536 7.63 11776
2048×2048 14.03 11776
3072×3072 31.97 11904
4096×4096 53.58 11904

AOT překladač Cython, základní varianta

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 9472
24×24 0.01 9472
32×32 0.01 9472
48×48 0.01 9600
64×64 0.02 9472
96×96 0.05 9600
128×128 0.07 9600
192×192 0.14 9600
256×256 0.27 9600
384×384 0.59 9600
512×512 1.06 9600
768×768 2.38 9600
1024×1024 4.24 9472
1536×1536 9.55 9600
2048×2048 16.62 9600
3072×3072 39.06 9472
4096×4096 69.38 9600

AOT překladač Cython, varianta s typovými informacemi

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 9472
24×24 0.01 9216
32×32 0.01 9344
48×48 0.01 9472
64×64 0.01 9472
96×96 0.01 9216
128×128 0.02 9344
192×192 0.02 9472
256×256 0.04 9472
384×384 0.09 9472
512×512 0.14 9344
768×768 0.32 9472
1024×1024 0.58 9472
1536×1536 1.31 9472
2048×2048 2.34 9472
3072×3072 5.26 9472
4096×4096 9.33 9344

AOT překladač Cython, plně optimalizovaná varianta

Rozlišení Čas [s] Paměť [kB]
16×16 0.01 9600
24×24 0.01 9856
32×32 0.01 9728
48×48 0.01 9856
64×64 0.01 9600
96×96 0.01 9600
128×128 0.01 9728
192×192 0.01 9600
256×256 0.02 9728
384×384 0.03 9600
512×512 0.05 9728
768×768 0.11 9728
1024×1024 0.18 9856
1536×1536 0.40 9728
2048×2048 0.71 9728
3072×3072 1.58 9600
4096×4096 2.77 9728

16. Vizualizace výsledků benchmarků

Mnohem přehlednější, než výsledky ve formě tabulky, je vizualizace benchmarků v podobě grafů:

Obrázek 4: Čas výpočtu v závislosti na rozlišení výsledného obrázku počítaného benchmarkem.

Obrázek 5: Vliv času startu aplikace. Zde vychází nejhůře Numba, protože se jedná o JIT překladač a je tedy nutné provést JIT překlad před vlastními výpočty.

Obrázek 6: Společně se zvětšujícím se rozlišením obrázku klesá vliv JIT překladu, ovšem zde (pro rozlišení do 512×512 pixelů) je stále jasně patrný – dokonce i běžný interpret je rychlejší než Numba.

Obrázek 7: Teprve pro velká rozlišení a tedy i delší dobu běhu výpočtu se ukazuje, že JIT překlad Numby je vlastně velmi dobrý a překonává Nuitku.

Obrázek 8: JIT překlad trvá cca 5 sekund, takže kratší skripty je lepší spouštět interpretrem a teprve delší JITovat (viz průsečíky křivek).

17. Porovnání výsledků benchmarků: interpretry

Obrázek 9: Porovnání rychlosti různých interpretrů Pythonu. Můžeme zde vidět výkonnostní skok mezi verzí 3.10 a 3.11 (což bylo taktéž oznámeno při vydávání), ovšem kupodivu je verze 3.12 nepatrně horší, než 3.11.

18. Porovnání výsledků benchmarků: překladače

Obrázek 10: Zde jsou zobrazeny jen výsledky céčkového kódu v porovnání s AOTovaným kódem, tedy vlastně vždy programy, které vznikly překladem céčkovského zdrojového kódu (ať již ručně napsaného či vzniklého transpilerem). Nuitka v tomto porovnání nevychází špatně – spadá do oblasti mezi nejhorším Cythonem a Cythonem s typovými informacemi.

Obrázek 11: Z tohoto grafu je patrné, jak dobrý překlad (resp. transpřeklad) dokáže provést Cython v případě, že má k dispozici typové informace.

19. Příloha: skript pro vytištění grafů s výsledky benchmarků

Všechny grafy, které jsme mohli vidět v kapitolách 17 a 18, byly vytvořeny s využitím následujícího skriptu, který je založen na knihovně Matplotlib. Následuje výpis zdrojového kódu tohoto pomocného skriptu:

#!/usr/bin/env python
# coding: utf-8
 
# ### Set of input files with benchmark results to be processed
input_files = (
    "native.times",
    "native_optim.times",
    "python_3_8.times",
    "python_3_9.times",
    "python_3_10.times",
    "python_3_11.times",
    "python_3_12.times",
    "mypyc_no_type_hints.times",
    "mypyc_with_type_hints.times",
    "numba2.times",
    "numba3.times",
    "numba4.times",
    "nuitka.times",
    "cython_basic.times",
    "cython_types.times",
    "cython_optim.times",
)
 
 
import pandas as pd
 
 
# ### Helper functions to read benchmark results
def read_benchmark_result(filename):
    return pd.read_csv(filename, sep=" ", header=0, names=("size", "time", "memory"))
 
 
def filename2description(filename):
    return filename.split(".")[0].replace("_", " ")
 
 
def read_all_results(input_files):
    return {
        filename2description(input_file): read_benchmark_result(input_file)
        for input_file in input_files
    }
 
 
# ### Combine all results into one DataFrame
r = read_all_results(input_files)
 
results = pd.DataFrame()
 
# column to be transformed into index
results["size"] = r["native"]["size"]
 
for description, df in r.items():
    results[description] = df["time"]
 
# create meaningful index
results.set_index("size", inplace=True)
results
 
 
# ### Set plot size
import matplotlib as mpl
import matplotlib.pyplot as plt
 
mpl.rcParams["figure.dpi"] = 150
mpl.rcParams["figure.figsize"] = (12, 4.8)
xticks = df.index
 
# color palette to be used
colormap = [
    "#a00000",
    "#a0a000",  # native
    "#00a000",
    "#00b030",
    "#00c060",
    "#00d090",
    "#00e0a0",  # Python interpreters
    "#ff0000",
    "#ff8000",  # mypyc
    "#0000ff",
    "#0060ff",
    "#00c0ff",  # Numba
    "#ff8080",  # Nuitka
    "#808080",
    "#a0a0a0",
    "#c0c0c0",  # Cython
]
 
 
# ### Plot results
 
# Startup times
results[0:5].plot(
    kind="bar", stacked=False, width=0.9, title="Startup time", color=colormap
)
plt.legend(loc=(1.04, 0))
plt.tight_layout()
plt.savefig("1st.png")
plt.show()
 
 
# Computation with some startup time influence
results[5:10].plot(
    kind="bar",
    stacked=False,
    width=0.9,
    title="Computation with startup time influence",
    color=colormap,
)
plt.legend(loc=(1.04, 0))
plt.tight_layout()
plt.savefig("Startup time and computation.png")
plt.show()
 
 
# Just the computation, w/o startup time
results[10:].plot(
    kind="bar", stacked=False, width=0.9, title="Extensive computation", color=colormap
)
plt.tight_layout()
plt.savefig("Extensive computation.png")
plt.show()
 
 
# Approximation, including startup time
results.plot(title="Approximation, incl. startup time", color=colormap)
plt.tight_layout()
plt.savefig("Approximation computation.png")
plt.show()
 
 
# Numba/CPython thresholds
results[8:12].plot(
    title="Numba/CPython thresholds",
    color=[
        "#a00000",
        "#a0a000",
        "#00a000",
        "#00b030",
        "#00c060",
        "#00d090",
        "#00e0a0",
        "#ff0000",
        "#ff8000",
        "#0000ff",
        "#0060ff",
        "#00c0ff",
    ],
)
plt.tight_layout()
plt.savefig("Numba CPython thresholds.png")
plt.show()
 
 
# Python interpreters only
results[11:][
    ["python 3 8", "python 3 9", "python 3 10", "python 3 11", "python 3 12"]
].plot(kind="bar", title="Python interpreters only", color=colormap[2:])
plt.tight_layout()
plt.savefig("Python interpreters only.png")
plt.show()
 
 
colormap = [
    "#a00000",
    "#a0a000",  # native
    "#ff0000",
    "#ff8000",  # mypyc
    "#8080ff",  # Nuitka
    "#808080",
    "#a0a0a0",
    "#c0c0c0",  # Cython
]
# Compiled code only
compiled = results[11:][
    [
        "native",
        "native optim",
        "mypyc no type hints",
        "mypyc with type hints",
        "nuitka",
        "cython basic",
        "cython types",
        "cython optim",
    ]
]
compiled.plot(kind="bar", color=colormap, title="Compiled code only")
plt.tight_layout()
plt.savefig("Compiled code only.png")
plt.show()
 
compiled.plot(title="Approximation, compiled", color=colormap)
plt.tight_layout()
plt.savefig("Approximation computation compiled.png")
plt.show()

20. Odkazy na Internetu

  1. Stránka projektu Nuitka
    https://nuitka.net/
  2. Dokumentace k projektu Nuitka
    https://nuitka.net/user-documentation/
  3. Nuitka na PyPi
    https://pypi.org/project/Nuitka/
  4. Cython (home page)
    http://cython.org/
  5. Cython (wiki)
    https://github.com/cython/cython/wiki
  6. Cython (Wikipedia)
    https://en.wikipedia.org/wiki/Cython
  7. Cython (GitHub)
    https://github.com/cython/cython
  8. Rychlost CPythonu 3.11 a 3.12 v porovnání s JIT a AOT překladači
    https://www.root.cz/clanky/rychlost-cpythonu-3–11-a-3–12-v-porovnani-s-jit-a-aot-prekladaci-pythonu/
  9. Rychlost CPythonu 3.11 a 3.12 v porovnání s JIT a AOT překladači Pythonu (2)
    https://www.root.cz/clanky/rychlost-cpythonu-3–11-a-3–12-v-porovnani-s-jit-a-aot-prekladaci-pythonu-2/
  10. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy/
  11. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (2.část)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-2-cast/
  12. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (3)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-3/
  13. Praktické použití nástroje Cython při překladu Pythonu do nativního kódu
    https://www.root.cz/clanky/prakticke-pouziti-nastroje-cython-pri-prekladu-pythonu-do-nativniho-kodu-1/
  14. Pyrex
    https://wiki.python.org/moin/Pyrex
  15. RPython vs Cython aneb dvojí přístup k překladu Pythonu do nativního kódu
    https://www.root.cz/clanky/rpython-vs-cython-aneb-dvoji-pristup-k-prekladu-pythonu-do-nativniho-kodu/
  16. Python Implementations: Compilers
    https://wiki.python.org/mo­in/PythonImplementations#Com­pilers
  17. EmbeddingCython
    https://github.com/cython/cyt­hon/wiki/EmbeddingCython
  18. The Basics of Cython
    http://docs.cython.org/en/la­test/src/tutorial/cython_tu­torial.html
  19. Overcoming Python's GIL with Cython
    https://lbolla.info/python-threads-cython-gil
  20. GlobalInterpreterLock
    https://wiki.python.org/mo­in/GlobalInterpreterLock
  21. The Magic of RPython
    https://refi64.com/posts/the-magic-of-rpython.html
  22. RPython: Frequently Asked Questions
    http://rpython.readthedoc­s.io/en/latest/faq.html
  23. RPython’s documentation
    http://rpython.readthedoc­s.io/en/latest/index.html
  24. RPython (Wikipedia)
    https://en.wikipedia.org/wi­ki/PyPy#RPython
  25. Getting Started with RPython
    http://rpython.readthedoc­s.io/en/latest/getting-started.html
  26. Duck typing
    https://en.wikipedia.org/wi­ki/Duck_typing
  27. PyPy (home page)
    https://pypy.org/
  28. PyPy (dokumentace)
    http://doc.pypy.org/en/latest/
  29. Localized Type Inference of Atomic Types in Python (2005)
    http://citeseer.ist.psu.e­du/viewdoc/summary?doi=10­.1.1.90.3231
  30. Numba
    http://numba.pydata.org/
  31. Tutorial: Writing an Interpreter with PyPy, Part 1
    https://morepypy.blogspot­.com/2011/04/tutorial-writing-interpreter-with-pypy.html
  32. List of numerical analysis software
    https://en.wikipedia.org/wi­ki/List_of_numerical_analy­sis_software
  33. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  34. 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/
  35. 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/
  36. PyPy is the Future of Python (článek z roku 2010)
    https://alexgaynor.net/2010/ma­y/15/pypy-future-python/
  37. Portal:Python programming
    https://en.wikipedia.org/wi­ki/Portal:Python_programming
  38. RPython Frontend and C Wrapper Generator
    http://www.codeforge.com/ar­ticle/383293
  39. PyPy’s Approach to Virtual Machine Construction
    https://bitbucket.org/pypy/ex­tradoc/raw/tip/talk/dls2006/py­py-vm-construction.pdf
  40. Tutorial: Writing an Interpreter with PyPy, Part 1
    https://morepypy.blogspot­.com/2011/04/tutorial-writing-interpreter-with-pypy.html
  41. A simple interpreter from scratch in Python (part 1)
    http://www.jayconrod.com/posts/37/a-simple-interpreter-from-scratch-in-python-part-1
  42. Brainfuck Interpreter in Python
    https://helloacm.com/brainfuck-interpreter-in-python/

Byl pro vás článek přínosný?