Rychlost CPythonu 3.11 a 3.12 v po­rovnání s JIT a AOT překladači

21. 11. 2023
Doba čtení: 37 minut

Sdílet

Autor: Depositphotos
Standardní interpret Pythonu verze 3.11 (CPython 3.11) je na základě benchmarků rychlejší, než předchozí verze. Naskýtá se tedy otázka, jak dobrý je CPython v porovnání s existujícími JIT a AOT překladači Pythonu.

Obsah

1. Rychlost CPythonu 3.11 a 3.12 v porovnání s JIT a AOT překladači Pythonu

2. JIT a AOT překladače Pythonu

3. AOT překladač mypyc

4. Instalace a první spuštění překladače mypyc

5. Příklad použití překladače mypyc

6. Spuštění kódu

7. Zjištění, které části kódu bylo možné přeložit optimálním způsobem

8. Doplnění všech typových informací

9. Poznámka k hodnotě __name__

10. JIT a AOT překladač Numba

11. (Mikro)benchmark: výpočty s využitím FP hodnot

12. Nativní varianta benchmarku přepsaná do ANSI C

13. Varianty upravené pro Mypyc

14. Varianty upravené pro Numbu

15. Výsledky benchmarků: rychlost výpočtů

16. Výsledky benchmarků: spotřeba operační paměti

17. Zdrojové kódy benchmarků použitých v dnešním článku

18. Repositář s demonstračními příklady pro nástroj Numba

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

20. Odkazy na Internetu

1. Rychlost Pythonu 3.11 a 3.12 v porovnání s JIT a AOT překladači Pythonu

Programovací jazyk Python zajisté není zapotřebí čtenářům Roota podrobně představovat. V současnosti se jedná o jeden z nejpoužívanějších a současně i nejpopulárnějších (což ovšem ani zdaleka není totéž) programovacích jazyků a v praxi se používá jak pro psaní jednorázových skriptů, tak i mnohdy velmi rozsáhlých aplikací. Jednou z nevýhod Pythonu je, resp. byl relativně pomalý běh aplikací psaných v tomto jazyku. Řekněme si to na rovinu: z mainstreamových jazyků vycházel klasický CPython většinou jako nejpomalejší technologie. Ovšem v současnosti již není situace vůbec špatná, protože existují JIT (just in time) i AOT (ahead of time) překladače Pythonu. A navíc i klasický CPython je neustále vylepšován, přičemž poměrně velký výkonnostní skok se odehrál u Python verze 3.11 (a některá vylepšení nalezneme i u verze 3.12).

V dnešním článku se přesvědčíme, jak rychlý či naopak pomalý je klasický CPython (verze 3.8 až 3.12) při provádění FP operací v porovnání s JIT překladačem Numba a taktéž v porovnání s AOT překladačem mypyc (což je nástroj, který je součástí Mypy). JIT překladačem Numba jsme se již na stránkách Roota zabývali, a to konkrétně v článcích Projekt Numba aneb další přístup k překladu Pythonu do nativního kódu a Just in time překlad programů psaných v Pythonu nástrojem Numba. Nástroj mypy prozatím nebyl popsán, takže se v navazujících kapitolách alespoň ve stručnosti zmíníme o jeho základních vlastnostech. Navážeme přitom na následující články o Mypy:

  1. 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/
  2. 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/
  3. 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/
Poznámka: důvodem, proč se zaměříme na CPython verze 3.8 až 3.12 a nikoli na starší verze Pythonu, je ten, že starší verze již oficiálně nejsou podporovány – viz též přehledná tabulka s podporovanými a nepodporovanými verzemi Pythonu, kterou lze najít na adrese https://devguide.python.or­g/versions/.

2. JIT a AOT překladače Pythonu

Klasický CPython byl původně navržen jako interpret bajtkódu virtuálního stroje Pythonu. Zdrojové kódy se před spuštěním přeložily do bajtkódu a ten byl interpretován, což je (podle očekávání) poměrně pomalé. Proto postupně vznikly překladače zajišťující překlad zdrojových kódu Pythonu do nativního kódu. Takový překlad lze provést v jediném kroku nebo ve více fázích (frontend a backend překladače). Navíc se od sebe překladače liší podle toho, zda je překlad proveden ještě před spuštěním aplikace (AOT – Ahead of Time) nebo ve chvíli, kdy se načítají jednotlivé balíčky nebo když se detekuje „horká“ část kódu (JIT – Just in Time). Každý ze zmíněných způsobů, tedy interpretace, JIT překlad a AOT překlad, má své výhody a zápory a obecně asi není možné říci, který z nich je obecně výhodnější (záleží na konkrétním způsobu použití).

Bez dalších podrobností si můžeme jednotlivé AOT a JIT překladače vypsat:

Překladač Typ Poznámka
Cython AOT nadmnožina Pythonu
Numba JIT (+AOT) viz další text
PyPy JIT  
mypyc AOT nepovinné typové informace
Psyco JIT již neudržován, zmíněn pouze pro úplnost
Unladen Swallow JIT pro CPython již nevyvíjen, některé vlastnosti jsou přímo v CPythonu

3. AOT překladač mypyc

Příkladem AOT překladače Pythonu je nástroj nazvaný mypyc. Ten je součástí instalace Mypy. Nástroj mypyc pracuje následujícím způsobem:

  1. Je provedena analýza původního Pythonovského kódu s jeho překladem do AST
  2. Na základě AST (s typy) je vygenerován zdrojový kód v jazyku C
  3. Tento kód je přeložen překladačem céčka (typicky gcc)
  4. Výsledkem je sdílená knihovna strukturovaná takovým způsobem, že ji lze využít jako C-extension jazyka Python

Výslednou sdílenou knihovnu (so – shared object) lze naimportovat do Pythonovského skriptu (odpovídá totiž specifikaci C-extension). Na rozdíl od dalších AOT překladačů tedy není výsledkem přímo spustitelný soubor, což opět přináší některé přednosti, ale i zápory.

4. Instalace a první spuštění překladače mypyc

Překladač mypyc je součástí balíčku Mypy (podpora a kontrola typových informací v Pythonu). Samotná instalace je snadná (předpokladem ovšem je, že je již nainstalován CPython verze 3.5 či vyšší):

$ pip3 install --user mypy
 
Collecting mypy
  Downloading mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.2 MB)
     |████████████████████████████████| 12.2 MB 779 kB/s
Requirement already satisfied: typing-extensions>=3.10 in ./.local/lib/python3.8/site-packages (from mypy) (4.4.0)
Collecting tomli>=1.1.0; python_version < "3.11"
  Downloading tomli-2.0.1-py3-none-any.whl (12 kB)
Collecting mypy-extensions>=1.0.0
  Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)
Installing collected packages: tomli, mypy-extensions, mypy
Successfully installed mypy-1.3.0 mypy-extensions-1.0.0 tomli-2.0.1

Většinou je ovšem ještě nutné provést upgrade balíčku nazvaného typing_extensions:

$ pip3 install --upgrade --user typing_extensions
 
Collecting typing_extensions
  Downloading typing_extensions-4.6.3-py3-none-any.whl (31 kB)
Installing collected packages: typing-extensions
  Attempting uninstall: typing-extensions
    Found existing installation: typing-extensions 4.4.0
    Uninstalling typing-extensions-4.4.0:
      Successfully uninstalled typing-extensions-4.4.0
Successfully installed typing-extensions-4.6.3

Otestování, zda je Mypy spustitelný:

$ mypy --version
 
mypy 1.3.0 (compiled: yes)

Nás ovšem dnes bude zajímat především mypyc. I ten by měl být dostupný:

$ mypyc --help
 
usage: mypy [-h] [-v] [-V] [more options; see below]
            [-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
 
Mypy is a program that will type check your Python code.
 
Pass in any files or folders you want to type check. Mypy will
recursively traverse any provided folders to find .py files:
 
    $ mypy my_program.py my_src_folder
 
For more information on getting started, see:
 
- https://mypy.readthedocs.io/en/stable/getting_started.html
 
For more details on both running mypy and using the flags below, see:
...
...
...

5. Příklad použití překladače mypyc

Podívejme se nyní na způsob použití AOT překladače mypyc. Necháme si přeložit výpočet Mandelbrotovy množiny (podrobnosti budou uvedeny níže). Výpočet přitom není nijak optimalizován a prozatím AOT překladači ani nijak „nepomůžeme“ specifikací datových typů. Už dopředu je možné naznačit, že kvůli chybějícím typovým informacím nebude výsledek ideální, ovšem úpravy (přidání typových informací) kupodivu v tomto případě nebudou nijak složité:

import palette_mandmap
from sys import argv
 
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 len(argv) < 4:
    width = 512
    height = 512
    maxiter = 255
else:
    width = int(argv[1])
    height = int(argv[2])
    maxiter = int(argv[3])
calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)

AOT překlad se provede takto:

$ mypyc mandelbrot_5.py
Poznámka: v názvu souboru nepoužívejte pomlčku ani další znaky, které mají v céčku speciální význam!

Samotný překlad je relativně rychlý:

running build_ext
building 'mandelbrot_5' extension
creating build/temp.linux-x86_64-cpython-311
creating build/temp.linux-x86_64-cpython-311/build
gcc -Wsign-compare -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DNDEBUG -O2 -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -O2 -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -O2 -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -fPIC -I/usr/local/lib64/python3.11/site-packages/mypyc/lib-rt -I/usr/include/python3.11 -c build/__native.c -o build/temp.linux-x86_64-cpython-311/build/__native.o -O3 -g1 -Werror -Wno-unused-function -Wno-unused-label -Wno-unreachable-code -Wno-unused-variable -Wno-unused-command-line-argument -Wno-unknown-warning-option -Wno-unused-but-set-variable -Wno-ignored-optimization-argument -Wno-cpp
creating build/lib.linux-x86_64-cpython-311
gcc -shared -Wl,-z,relro -Wl,--as-needed -Wl,-z,now -Wl,--build-id=sha1 -Wl,-z,relro -Wl,--as-needed -Wl,-z,now -Wl,--build-id=sha1 build/temp.linux-x86_64-cpython-311/build/__native.o -L/usr/lib64 -o build/lib.linux-x86_64-cpython-311/mandelbrot_5.cpython-311-x86_64-linux-gnu.so
copying build/lib.linux-x86_64-cpython-311/mandelbrot_5.cpython-311-x86_64-linux-gnu.so ->

Výsledkem by měl být tento soubor (koncovka napovídá, že se jedná o sdílenou knihovnu):

-rwxrwxr-x   1 ptisnovs ptisnovs  366664 Nov 18 10:22 mandelbrot_5.cpython-38-x86_64-linux-gnu.so

Současně se v průběhu překladu vytvořil i podadresář build, který však nebudeme dále potřebovat. Použít ho lze ve chvíli, kdy je nutné výše zmíněnou knihovnu nainstalovat (což však nebudeme potřebovat):

build
├── lib.linux-x86_64-cpython-311
│   └── mandelbrot_5.cpython-311-x86_64-linux-gnu.so
├── __native.c
├── __native.h
├── __native_internal.h
├── ops.txt
├── setup.py
└── temp.linux-x86_64-cpython-311
    └── build
        └── __native.o
 
4 directories, 8 files

6. Spuštění kódu

Samotné spuštění kódu, který by měl vykreslit Mandelbrotou množinu, se provede takto:

$ python3 -c "import mandelbrot_5" 400 400 255 > mandelbrot.ppm

Výsledkem by měl být tento obrázek:

Obrázek 1: Výsledek běhu programu, jenž vznikl AOT překladem Pythonovského kódu.

O tom, že výše uvedený příkaz nebude importovat soubor mandelbrot5.py, ale nativní sdílenou knihovnu, se můžeme přesvědčit například použitím nástroje strace:

$ strace python3 -c "import mandelbrot_5" 400 400 255 > mandelbrot.ppm

V zobrazených zprávách lze najít mj. i tyto informace ukazující, že se skutečně načetla sdílená knihovna:

execve("/usr/bin/python3", ["python3", "-c", "import mandelbrot_5"], 0x7ffd0f6a5670 /* 60 vars */) = 0
...
...
...
stat("/tmp/ramdisk/mandelbrot_5.cpython-38-x86_64-linux-gnu.so", {st_mode=S_IFREG|0775, st_size=366664, ...}) = 0
futex(0x7f4d65b320c8, FUTEX_WAKE_PRIVATE, 2147483647) = 0
openat(AT_FDCWD, "/tmp/ramdisk/mandelbrot_5.cpython-38-x86_64-linux-gnu.so", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\205\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0775, st_size=366664, ...}) = 0
mmap(NULL, 248488, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4d64fbf000
mmap(0x7f4d64fc6000, 192512, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x7000) = 0x7f4d64fc6000
mmap(0x7f4d64ff5000, 20480, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x36000) = 0x7f4d64ff5000
mmap(0x7f4d64ffa000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3a000) = 0x7f4d64ffa000
close(3)                                = 0

7. Zjištění, které části kódu bylo možné přeložit optimálním způsobem

Překladač mypyc se snaží o překlad veškerého zdrojového kódu, který je mu předán. Ovšem pokud nemá k dispozici typové informace, nebude výsledek optimální. Proto je vhodné zjistit, které části kódu nejsou přeloženy optimálně kvůli tomu, že jsme AOT překladači nedodali všechny důležité typové informace. Toto zjištění je snadné a můžeme ho získat v několika formátech (textový, HTML, formát kompatibilní s Coberturou atd.).

Celkový přehled lze získat příkaze:

$ mypyc --txt-report report mandelbrot_5.py

Výsledkem bude tato tabulka:

Mypy Type Check Coverage Summary
================================
 
Script: index
 
+--------------+-------------------+--------+
| Module       | Imprecision       | Lines  |
+--------------+-------------------+--------+
| mandelbrot_5 |  63.41% imprecise | 41 LOC |
+--------------+-------------------+--------+
| Total        |  63.41% imprecise | 41 LOC |
+--------------+-------------------+--------+

Podrobnější čitelný výsledek získáme příkazem:

$ mypyc --html-report html mandelbrot_5.py

Výsledkem bude HTML stránka, kde jsou zvýrazněny řádky přeložené optimálně i ty řádky, které nebylo možné optimálně přeložit:

Obrázek 2: Optimálně a neoptimálně přeložené řádky původního skriptu.

8. Doplnění všech typových informací

Ve skutečnosti je doplnění typových informací v našem konkrétním případě relativně snadné a změny se týkají pouze hlavičky funkce s výpočtem fraktálu. Náš kód bude po úpravách vypadat takto (změna se týká jen zvýrazněného řádku):

import palette_mandmap
from sys import argv
 
from typing import Tuple
 
 
def calc_mandelbrot(width: int, height: int, maxiter: int, palette: Tuple[Tuple[int, int, int], ...]) -> None:
    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 len(argv) < 4:
    width = 512
    height = 512
    maxiter = 255
else:
    width = int(argv[1])
    height = int(argv[2])
    maxiter = int(argv[3])
calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)

Snadno lze zjistit, že je nyní optimálně přeložen veškerý kód:

Mypy Type Check Coverage Summary
================================
 
Script: index
 
+--------------+-------------------+--------+
| Module       | Imprecision       | Lines  |
+--------------+-------------------+--------+
| mandelbrot_6 |   0.00% imprecise | 44 LOC |
+--------------+-------------------+--------+
| Total        |   0.00% imprecise | 44 LOC |
+--------------+-------------------+--------+

Obrázek 3: Všechny řádky jsou nyní přeloženy optimálním způsobem.

9. Poznámka k hodnotě __name__

Při AOT překladu již existujících skriptů můžeme narazit na jeden problém, který je ovšem snadno řešitelný (a vlastně se nejedná o problém, ale o potenciálně výhodnou vlastnost). Týká se to hodnoty __name__, která má při běžném spuštění Pythonovského skriptu hodnotu __main__, o čemž se můžeme snadno přesvědčit spuštěním následujícího jednořádkového skriptu:

print(__name__)

Při přímém spuštění (interpretaci) tohoto skriptu dostaneme očekávaný výstup:

$ python3 print_name.py
 
__main__

Při importu se ovšem vypíše odlišné jméno:

$ python3 -c "import print_name"
 
print_name

Totéž ovšem platí po AOT překladu, protože výslednou sdílenou knihovnu nelze „spustit“ ale importovat:

$ mypyc print_name.py
running build_ext
copying build/lib.linux-x86_64-3.8/print_name.cpython-38-x86_64-linux-gnu.so ->
 
$ rm print_name.py
 
$ python3 -c "import print_name"
 
print_name

Co to znamená v praxi? Skripty, jejichž kód, který se má spustit, je „schovaný“ před importem, budou pouze importovány, ale takový kód se nespustí:

def main():
    ...
    ...
    ...
 
if __name__ == "__main__":
    main() # nespustí se
Poznámka: z tohoto důvodu benchmarky použité dnes tento způsob nepoužívají.

10. JIT a AOT překladač Numba

Nástroj Numba podporuje překlad vybraných částí kódu aplikace psané v Pythonu do nativního kódu cílové platformy (x86–64 apod.), přičemž cílovou platformou může být i GPU (přes CUDA). Jedná se tedy o takzvaný JIT neboli o just-in-time překladač, který má tu výhodu, že dokáže odvodit datové typy proměnných a argumentů funkcí na základě skutečného chování aplikace, tedy na základě typů předávaných parametrů a kontextu. To samozřejmě neznamená, že by JIT již při prvním volání funkce přesně věděl, jak má funkci přeložit.

Ve skutečnosti se dozví pouze informace o jediné konkrétní větvi, kterou může přeložit. V případě, že bude ta samá funkce později volána s odlišnými typy parametrů, popř. se její chování změní jiným způsobem (Python je velmi dynamický jazyk), provede se just-in-time překlad znovu, takže zde zaplatíme za vyšší výpočetní výkon poněkud většími paměťovými nároky a pomalejším během prvních volání funkce. Na druhou stranu mnoho náročných výpočtů používá Numpy a Numba s Numpy dokáže spolupracovat velmi dobře.

Z pohledu běžného vývojáře je největší předností tohoto způsobu překladu fakt, že není zapotřebí samotný zdrojový kód měnit (až na uvedení anotace před funkci). Nepříjemný je přesun času překladu do runtime, což sice nevadí u aplikací, které běží delší dobu, ovšem u jednorázových skriptů může být použití JITu spíše kontraproduktivní – vliv JIT překladu ostatně uvidíme na výsledku benchmarků.

Samotný překlad je prováděn na několika úrovních, přičemž Numba na nižších úrovních využívá možností nabízených LLVM. Jedná se o relativně složitou problematiku, které se budeme věnovat v samostatném článku.

Pro účely benchmarků si nainstalujeme i nástroj Numba:

$ sudo python3 -m pip install --upgrade pip
 
Collecting pip
  Downloading https://files.pythonhosted.org/packages/a4/6d/6463d49a933f547439d6b5b98b46af8742cc03ae83543e4d7688c2420f8b/pip-21.3.1-py3-none-any.whl (1.7MB)
    100% |████████████████████████████████| 1.7MB 764kB/s
Installing collected packages: pip
  Found existing installation: pip 9.0.3
    Uninstalling pip-9.0.3:
      Successfully uninstalled pip-9.0.3
Successfully installed pip-21.3.1

11. (Mikro)benchmark: výpočty s využitím FP hodnot

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 ve skutečnosti 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. Budeme testy měřit kód s vnořenými smyčkami, podmínkou ve smyčce, FP výpočty atd. a nikoli například rychlost implementace přidávání prvků do seznamu či zjišťování, zda řetězec odpovídá regulárnímu výrazu (k tomu se můžeme dostat později). Následuje výpis zdrojového kódu benchmarku. Kód je přitom napsán tak, aby byl kompatibilní s Pythonem 3.x, Jythonem, mypyc i s Numbou:

#!/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:
        width = 512
        height = 512
        maxiter = 255
    else:
        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čí.

Poznámka: prozatím nebudeme řešit, jak by bylo možné výpočet urychlit například paralelním vykonáním výpočtů. Tomuto tématu se budeme věnovat v samostatném článku.

12. Nativní varianta benchmarku přepsaná do 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;
}

13. Varianty upravené pro Mypyc

Výše uvedený pythonovský skript byl pro potřeby mypyc upraven jen minimálně. Pouze došlo k tomu, že se výpočet přímo spustí i v případě importu skriptu (nebo jeho varianty přeložené do nativního kódu). Takto upravený zdrojový kód byl vypsán v páté kapitole. Druhá úprava spočívá v přidání typových informací. Takto upravený zdrojový kód byl vypsán v osmé kapitole a opět ho tedy nebudeme znovu uvádět. Žádné další úpravy specifické pro mypyc nebyly provedeny.

Obrázek 4: Detail Mandelbrotovy množiny vypočtené skriptem přeloženým nástrojem Mypyc do nativního kódu.

14. Varianty upravené pro Numbu

Nepatrné úpravy zdrojového kódu benchmarku si vyžaduje i nástroj Numba (opět platí – v IT nedostaneme prakticky nic zadarmo).

Prvním krokem při praktickém použití nástroje Numba je zápis anotace @jit před funkcí, u které potřebujeme, aby ji překladač optimalizoval v čase běhu. Nejdříve musíme do příslušného modulu anotaci naimportovat, což je snadné:

from numba import jit

Následně tuto anotaci použijeme – žádné další kroky není zapotřebí provést:

@jit
def calc_mandelbrot(width, height, maxiter, palette):
    ...
    ...
    ...

Jedním z potenciálně problematických prvků našeho benchmarku je použití standardní pythonovské funkce print. Při JIT překladu totiž může Numba použít dvě varianty této funkce – původní „univerzální“ pythonovskou variantu s téměř nepřebernými možnostmi formátování a volitelnými parametry nebo zjednodušenou variantu umožňující tisk číselných hodnot nebo řetězců (více info o nativních funkcích Numby je uvedeno na stránce https://numba.pydata.org/numba-doc/dev/reference/pysupported.html). Obecně platí, že pokud použijeme zjednodušenou variantu funkce print, bude JIT schopen přeložit celou JITovanou funkci do strojového kódu.

Náš kód tedy na dvou místech nepatrně upravíme a využijeme tak velké flexibility formátu PNM, v němž je možné použít jako oddělovač buď konec řádku nebo libovolný bílý znak (jednou z nepříjemných vlastností nativní varianty printu je to, že se vždy tiskne konec řádku).

Původní kód:

print("P3")
print("{w} {h}".format(w=width, h=height))
print("255")

Nový kód:

print("P3")
print(width)
print(height)
print("255")

Původní kód:

r = palette[i][0]
g = palette[i][1]
b = palette[i][2]
print("{r} {g} {b}".format(r=r, g=g, b=b))

Nový kód:

r = palette[i][0]
g = palette[i][1]
b = palette[i][2]
print(r)
print(g)
print(b)
Poznámka: to, jaká varianta funkce print se zvolí, nemusíme explicitně zadávat; JIT výběr provede za nás.

Nová podoba benchmarku tedy bude následující:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import palette_mandmap
from sys import argv, exit
 
from numba import jit
 
 
@jit
def calc_mandelbrot(width, height, maxiter, palette):
    print("P3")
    print(width)
    print(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)
            print(g)
            print(b)
            cx += 3.0/width
        cy += 3.0/height
 
 
if __name__ == "__main__":
    if len(argv) < 4:
        width = 512
        height = 512
        maxiter = 255
    else:
        width = int(argv[1])
        height = int(argv[2])
        maxiter = int(argv[3])
    calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)

JIT ve skutečnosti může pracovat ve dvou režimech, které se nazývají object mode a nopython mode. V prvním režimu je kód vytvářený JITem schopný zpracovat libovolné objekty (resp. reference na ně) a v případě potřeby se v kódu volá C API Pythonu pro zpracování těchto objektů. Pokud je tento režim použit, nebude se rychlost výsledného programu příliš odlišovat od běhu interpretru. Z tohoto důvodu se většinou budeme chtít tomuto režimu vyhnout – pokud to půjde. Naproti tomu druhý režim (nopython mode) generuje kód, v němž se C API nevolá a všechny proměnné a argumenty nesou hodnoty nativních typů (int, double atd.). Tento režim si můžeme vynutit anotací @jit(nopython=True), ovšem s několika omezeními, které se týkají například výše zmíněné funkce print (ostatně si zkuste sami vyzkoušet, co se stane, pokud tuto anotaci přidáme do prvního příkladu).

Benchmark upravíme následujícím způsobem:

@jit(nopython=True)
def calc_mandelbrot(width, height, maxiter, palette):

Pro jistotu si uveďme celý kód, jak s novou anotací, tak i s použitím zjednodušené nativní funkce print:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import palette_mandmap
from sys import argv, exit
 
from numba import jit
 
 
@jit(nopython=True)
def calc_mandelbrot(width, height, maxiter, palette):
    print("P3")
    print(width)
    print(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)
            print(g)
            print(b)
            cx += 3.0/width
        cy += 3.0/height
 
 
if __name__ == "__main__":
    if len(argv) < 4:
        width = 512
        height = 512
        maxiter = 255
    else:
        width = int(argv[1])
        height = int(argv[2])
        maxiter = int(argv[3])
    calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)

15. Výsledky benchmarků: rychlost výpočtů

Nyní již máme hned několik variant zdrojových kódů benchmarků:

  1. Původní zdrojový kód pro klasický interpret Pythonu
  2. Kód přepsaný do ANSI C
  3. Kód určený pro AOT překlad nástrojem mypyc bez typových informací (liší se způsobem spuštění)
  4. Kód určený pro AOT překlad nástrojem mypyc s přidanými typovými informacemi
  5. Numba: kód, do něhož byla pouze přidána anotace @jit
  6. Numba: varianta s jednodušší (nativní) funkcí print
  7. Numba: varianta s jednodušší (nativní) funkcí print a anotací @jit(nopython=True)

Tyto zdrojové kódy použijeme pro spuštění celkem dvanácti benchmarků, jejichž označení a význam je zapsán v tabulce:

Označení Stručný popis benchmarku
native benchmark přepsaný do ANSI C, překlad bez optimalizací
native optim benchmark přepsaný do ANSI C, překlad s optimalizacemi
python 3.8 benchmark spuštěný standardním CPythonem verze 3.8
python 3.9 benchmark spuštěný standardním CPythonem verze 3.9
python 3.10 benchmark spuštěný standardním CPythonem verze 3.10
python 3.11 benchmark spuštěný standardním CPythonem verze 3.11
python 3.12 benchmark spuštěný standardním CPythonem verze 3.12
mypyc no type hints kód určený pro AOT překlad nástrojem mypyc bez typových informací
mypyc with type hints kód určený pro AOT překlad nástrojem mypyc s přidanými typovými informacemi
numba 2 Numba: kód, do něhož byla pouze přidána anotace @jit
numba 3 Numba: varianta s jednodušší (nativní) funkcí print
numba 4 Numba: varianta s jednodušší (nativní) funkcí print a anotací @jit(nopython=True)
Poznámka: optimalizovaný nativní kód jako jediný neprodukuje totožné výsledky! Je tomu tak kvůli použití -ffast-math, takže FP operace přesně neodpovídají IEEE 754. Všechny ostatní kódy produkují výsledky, které jsou naprosto totožné (z pohledu výsledné bitmapy s vypočteným obrázkem).
Poznámka2: dnes se soustředíme jen na test rychlosti běhu programu v jediném procesu a jediném vláknu. Nevyužijeme zde tedy možností nabízených nástrojem Numba v (polo)automatické paralelizaci běhu kódu.

První graf ukazuje časy běhu všech benchmarků. Zvolil jsem liniový graf, který naznačuje, jak dobré či špatné jsou jednotlivé implementace nejenom pro dlouhé výpočty, ale i při započítání času inicializace procesu. Na horizontální osu jsou vyneseny velikosti bitmap (x×y pixelů), na vertikální osu pak doby běhu v sekundách:

Obrázek 5: Časy běhu všech benchmarků v závislosti na požadovaném rozlišení výsledné bitmapy.

Poznámka: z grafu plyne minimálně jedno poučení – nepoužívejte nástroj Numba bez dalších úprav zdrojových kódů! Zejména ne tehdy, pokud Numba vypisuje varování, že používá „objektový režim“.

Na druhém grafu jsou zobrazeny časy běhu benchmarků pro malé bitmapy. Právě zde nehrají výpočty prakticky žádnou významnou roli, mnohem více se zde projeví inicializace procesu. To v případě nástroje Numba znamená JIT překlad. Jak je patrné, ten trvá přibližně 4 sekundy:

Obrázek 6: Časy běhu benchmarků pro malé bitmapy. Zde se nejvíce projeví čas inicializace procesu.

Poznámka: další poučení – pro velmi krátké skripty není použití nástroje Numba ideální, a to kvůli času, který se tráví JITováním kódu.

Pro větší velikosti bitmap se již kromě času JITování projeví i časy výpočtů. Stále se však pohybujeme v celkovém času dosahujícím jednotky sekund, takže JITování zde hraje spíše negativní roli:

Obrázek 7: Časy běhu benchmarků pro středně velké bitmapy.

Pro ještě větší bitmapy však dostaneme zcela odlišné výsledky, protože JITovaný kód zde překonává jak klasické interpretry (což se dalo čekat), tak i Mypy!:

Obrázek 8: Časy běhu benchmarků pro velké bitmapy.

Poznámka: poučení – JIT realizovaný v nástroji Numba je ve skutečnosti účinný, ale jen pro déletrvající výpočty (desítky sekund v původním interpretru). Další poučení – výsledky mypyc nejsou nijak oslňující, a to i v porovnání s klasickými interpretry! (což mě osobně dosti překvapilo)

Ještě se podívejme na pohled na „sweet spot“, kde rychlost JITovaného kódu překročí rychlost kódu interpretovaného:

Obrázek 9: Oblast, v níž se JITovaný kód stává rychlejším, než kód interpretovaný.

V úvodní části článku jsme se taktéž zmínili o tom, že Python 3.11 (a 3.12) je obecně rychlejší, než předchozí interpretry Pythonu – tedy alespoň podle tvrzení tvůrců. Podívejme se, zda je tomu skutečně tak:

Obrázek 10: Porovnání rychlostí interpretrů Pythonu (CPython 3.x).

Poznámka: zde je poučení jednoznačné – Python 3.11 a Python 3.12 jsou skutečně značně rychlejší, než předchozí verze. Nejedná se sice o řádové vylepšení, ale o cca 50% nárůst výpočetního výkonu v tomto mikrobenchmarku zaměřeného na FP operace. Zde se vyplatí přejít co nejrychleji :-)

16. Výsledky benchmarků: spotřeba operační paměti

V dnešním článku jsme se sice primárně zaměřili na měření rychlosti FP výpočtů (v jediném vláknu), ale možná stojí zato se podívat i na spotřebu operační paměti pro jednotlivé skupiny benchmarků. Zde je situace snadná, protože všechny benchmarky lze zhruba rozdělit do dvou kategorií – nativní kód, kód běžící v libovolné verzi CPythonu a nativní kód přeložený pomocí mypyc na straně jedné a benchmarky JITované nástrojem Numba na straně druhé:

Obrázek 11: Spotřeba operační paměti.

Poznámka: Numba má až překvapivě vysoké nároky na spotřebu operační paměti. Ta neroste s velikostí úlohy (ostatně nic nealokujeme), ale je závislá na velikosti JIT překladače i na velikosti JITovaného kódu, který je díky rozbalení smyček poměrně rozsáhlý.

Víme již, že Python 3.11 a Python 3.12 je rychlejší, než jeho předchůdci. Na druhou stranu mají tyto dvě nejnovější verze nepatrně větší spotřebu operační paměti. Nejedná se ovšem o nic podstatného, jak je to ostatně patrné z dalšího grafu (ovšem pro porovnání Numbou porovnejte s grafem předchozím):

Obrázek 12: Spotřeba operační paměti, zaměřeno na klasické interpretry Pythonu.

Poznámka na závěr: tyto mikrobenchmarky ovšem neukazují další vlastnosti nástrojů mypyc a především pak Numby – možnost paralelizace kódu. Jedná se o „detail“ s velkým praktickým dopadem, kterému se budeme věnovat příště.

17. Zdrojové kódy benchmarků použitých v dnešním článku

Pro změření výkonnosti různých variant spuštění projektů naprogramovaných v Pythonu bylo použito celkem sedm verzí zdrojových kódů benchmarku. První verze je určena pro klasický CPython (my jsme využili verze 3.8 až 3.12), další tři verze jsou určeny pro použití společně s JIT překladačem Numba. Následují dvě verze určené pro AOT překladač Mypy a konečně poslední verze byla přepsána do ANSI C, abychom mohli porovnat, jak může být sémanticky totožný kód rychlejší při použití odlišného ekosystému:

# Příklad 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

18. Repositář s demonstračními příklady pro nástroj Numba

Všechny demonstrační příklady ukazující vlastnosti nástroje Numba naleznete v repositáři https://github.com/tisnik/most-popular-python-libs:

# Příklad Stručný popis Adresa
1 sum1.py funkce pro výpočet součtu dvou prvků, které je volána s argumenty typu int64 https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum1.py
2 sum2.py funkce pro výpočet součtu dvou prvků, které je volána s argumenty typu double/float64 https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum2.py
3 sum3.py funkce pro výpočet součtu dvou prvků volaná s argumenty různých typů https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum3.py
4 sum4.py součet dvou polí z balíčku Numpy https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum4.py
5 sum5.py porovnání různých variant součtu dvou polí https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum5.py
6 sum6.py dtto, ale pro větší počet volání funkce pro provedení součtu https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum6.py
     
7 sum_array.py explicitní zápis součtu všech prvků pole https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum_array.py
8 range_loop.py využití funkce range ve smyčce for https://github.com/tisnik/most-popular-python-libs/blob/master/numba/range_loop.py
9 prange_loop.py paralelní varianta funkce range ve smyčce for https://github.com/tisnik/most-popular-python-libs/blob/master/numba/prange_loop.py
10 sum_sqrts1.py výpočet prováděný ve smyčce s akumulací výsledku https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum_sqrts1.py
11 sum_sqrts2.py dtto, ovšem provedení v režimu nopython https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum_sqrts2.py
12 sum_sqrts3.py zapnutí režimu fast array při výpočtech https://github.com/tisnik/most-popular-python-libs/blob/master/numba/sum_sqrts3.py

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

Všechny Pythonovské skripty, které jsme si popsali v článcích o Mypy, naleznete opět na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je pochopitelně nutné mít nainstalován balíček mypy společně s Pythonem alespoň 3.7):

# Příklad Stručný popis Adresa příkladu
1 adder1.py funkce add bez typových anotací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder1.py
2 adder2.py funkce add s typovými anotacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder2.py
3 adder3.py funkce add volaná s hodnotami TrueFalse https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder3.py
4 adder4.py funkce add akceptující hodnoty typu bool https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder4.py
5 adder5.py zobrazení typových informací pro funkci bez typových anotací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder5.py
6 adder6.py zobrazení typových informací pro funkci s typovými anotacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder6.py
       
5 exec_problem1.py funkce add s typovými anotacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/exec_pro­blem1.py
6 exec_problem2.py korektní detekce volání funkce add s nekompatibilními hodnotami https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/exec_pro­blem2.py
7 exec_problem3.py příkaz použitý v exec není statickým analyzátorem zachycen https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/exec_pro­blem3.py
       
8 list_type1.py typ seznam, s inicializací (bez prvků), pro Python 3.10 https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type1.py
9 list_type2.py typ seznam, s inicializací (bez prvků), pro starší verze Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type2.py
10 list_type3.py typ seznam, s inicializací (s prvky), pro starší verze Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type3.py
11 list_type4.py typ seznam, kontrola použití prvků s nekorektními typy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type4.py
12 list_type5.py typ seznam, kontrola použití prvků s korektními typy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type5.py
13 list_type6.py typ seznam, kontrola použití prvků s korektními typy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type6.py
       
14 tuple_type1.py typ n-tice (nekorektní specifikace typu) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type1.py
15 tuple_type2.py typ n-tice (korektní specifikace typu) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type2.py
16 tuple_type3.py typ n-tice, v níž má každý prvek odlišný typ https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type3.py
17 tuple_type4.py typ n-tice, v níž má každý prvek odlišný typ https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type4.py
       
18 json_check.py delší kód v Pythonu bez typových anotací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/json_check.py
       
19 Variance1.java variance v Javě – korektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance1.java
20 Variance2.java variance v Javě – nekorektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance2.java
       
21 Variance1.py variance v Pythonu – korektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance1.py
22 Variance2.py variance v Pythonu – nekorektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance2.py
23 Variance3.py variance v Pythonu – nekorektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance3.py
24 Variance4.py použití typu Sequence namísto List https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance4.py
       
25 view_pyc.py jednoduchá prohlížečka souborů .pyc https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/view_pyc.py
       
26 callable1.py funkce s typovými informacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable1.py
27 callable2.py variance funkcí https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable2.py
28 callable3.py variance funkcí (nekorektní příklad) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable3.py
29 callable4.py korektní řešení problému z kódu callable3.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable4.py
       
30 dict_type1.py slovník bez specifikace informací o typech (homogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type1.py
31 dict_type2.py slovník bez specifikace informací o typech (heterogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type2.py
32 dict_type3.py typově silný slovník (heterogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type3.py
33 dict_type4.py typově silný slovník (homogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type4.py
34 dict_type5.py použití typu Union https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type5.py
35 dict_type6.py použití typu Union https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type6.py
36 dict_type7.py použití typu Optional https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type7.py
37 dict_type8.py použití typu Optional https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type8.py
       
38 sprites1.py původní kód před refaktoringem a bez typových informací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites1.py
39 sprites2.py refaktoring kódu sprites1.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites2.py
40 sprites3.py přidání typových informací do kódu sprites2.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites3.py
41 sprites4.py oprava chyb nalezených v kódu sprites3.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites4.py
42 sprites5.py doplnění generických typů pro volbu –strict https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites5.py
43 sprites6.py definice a použití nového datového typu https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites6.py
       
44 reveal_type1.py selektivní zjištění typu proměnné či třídy nástrojem Mypy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type1.py
45 reveal_type2.py selektivní zjištění typu funkce nástrojem Mypy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type2.py
46 reveal_type3.py typová inference https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type3.py
47 reveal_type4.py typová inference https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type4.py
48 reveal_type5.py explicitní definice typu versus typová inference https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type5.py
       
49 reveal_locals1.py zjištění typů všech lokálních proměnných https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reve­al_locals1.py
50 reveal_locals2.py zjištění typů všech lokálních proměnných https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reve­al_locals2.py
51 reveal_locals3.py zjištění typů všech lokálních proměnných https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reve­al_locals3.py
       
52 list_sequence.py měnitelné seznamy vs. neměnitelné sekvence https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_sequence.py
53 dict_mapping.py měnitelné slovníky vs. neměnitelné mapování https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_mapping.py

20. Odkazy na Internetu

  1. Python 3.12: More Faster and More Efficient Python
    https://medium.com/@HeCanThink/python-3–12-more-faster-and-more-efficient-python-b636f00b047
  2. 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/
  3. 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/
  4. 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/
  5. mypy homepage
    https://www.mypy-lang.org/
  6. mypy documentation
    https://mypy.readthedocs.i­o/en/stable/
  7. Mypy na PyPi Optional static typing for Python
    https://pypi.org/project/mypy/
  8. 5 Reasons Why You Should Use Type Hints In Python
    https://www.youtube.com/wat­ch?v=dgBCEB2jVU0
  9. Python Typing – Type Hints & Annotations
    https://www.youtube.com/watch?v=QORvB-_mbZ0
  10. What Problems Can TypeScript Solve?
    https://www.typescriptlang.org/why-create-typescript
  11. How to find code that is missing type annotations?
    https://stackoverflow.com/qu­estions/59898490/how-to-find-code-that-is-missing-type-annotations
  12. Do type annotations in Python enforce static type checking?
    https://stackoverflow.com/qu­estions/54734029/do-type-annotations-in-python-enforce-static-type-checking
  13. Understanding type annotation in Python
    https://blog.logrocket.com/un­derstanding-type-annotation-python/
  14. Static type checking with Mypy — Perfect Python
    https://www.youtube.com/wat­ch?v=9gNnhNxra3E
  15. Static Type Checker for Python
    https://github.com/microsoft/pyright
  16. Differences Between Pyright and Mypy
    https://github.com/microsof­t/pyright/blob/main/docs/my­py-comparison.md
  17. 4 Python type checkers to keep your code clean
    https://www.infoworld.com/ar­ticle/3575079/4-python-type-checkers-to-keep-your-code-clean.html
  18. Pyre: A performant type-checker for Python 3
    https://pyre-check.org/
  19. „Typing the Untyped: Soundness in Gradual Type Systems“ by Ben Weissmann
    https://www.youtube.com/wat­ch?v=uJHD2×yv7×o
  20. Covariance and contravariance (computer science)
    https://en.wikipedia.org/wi­ki/Covariance_and_contrava­riance_(computer_science)
  21. Functional Programming: Type Systems
    https://www.youtube.com/wat­ch?v=hy1wjkcIBCU
  22. A Type System From Scratch – Robert Widmann
    https://www.youtube.com/wat­ch?v=IbjoA5×VUq0
  23. „Type Systems – The Good, Bad and Ugly“ by Paul Snively and Amanda Laucher
    https://www.youtube.com/wat­ch?v=SWTWkYbcWU0
  24. Type Systems: Covariance, Contravariance, Bivariance, and Invariance explained
    https://medium.com/@thejameskyle/type-systems-covariance-contravariance-bivariance-and-invariance-explained-35f43d1110f8
  25. Statická vs. dynamická typová kontrola
    https://www.root.cz/clanky/staticka-dynamicka-typova-kontrola/
  26. Typový systém
    https://cs.wikipedia.org/wi­ki/Typov%C3%BD_syst%C3%A9m
  27. Comparison of programming languages by type system
    https://en.wikipedia.org/wi­ki/Comparison_of_programmin­g_languages_by_type_system
  28. Flow
    https://flow.org/
  29. TypeScript
    https://www.typescriptlang.org/
  30. Sorbet
    https://sorbet.org/
  31. Pyright
    https://github.com/microsoft/pyright
  32. Mypy: Type hints cheat sheet
    https://mypy.readthedocs.i­o/en/stable/cheat_sheet_py3­.html
  33. PEP 484 – Type Hints
    https://peps.python.org/pep-0484/
  34. Numba
    http://numba.pydata.org/
  35. numba 0.57.0
    https://pypi.org/project/numba/
  36. Pushing Python toward C speeds with SIMD
    https://laurenar.net/posts/python-simd/
  37. Retrieve generated LLVM from Numba
    https://stackoverflow.com/qu­estions/25213137/retrieve-generated-llvm-from-numba
  38. Numba documentation
    http://numba.pydata.org/numba-doc/latest/index.html
  39. Numba na GitHubu
    https://github.com/numba/numba
  40. First Steps with numba
    https://numba.pydata.org/numba-doc/0.12.2/tutorial_firststeps.html
  41. Numba and types
    https://numba.pydata.org/numba-doc/0.12.2/tutorial_types.html
  42. Just-in-time compilation
    https://en.wikipedia.org/wiki/Just-in-time_compilation
  43. Cython (home page)
    http://cython.org/
  44. Cython (wiki)
    https://github.com/cython/cython/wiki
  45. Cython (Wikipedia)
    https://en.wikipedia.org/wiki/Cython
  46. Cython (GitHub)
    https://github.com/cython/cython
  47. Python Implementations: Compilers
    https://wiki.python.org/mo­in/PythonImplementations#Com­pilers
  48. EmbeddingCython
    https://github.com/cython/cyt­hon/wiki/EmbeddingCython
  49. The Basics of Cython
    http://docs.cython.org/en/la­test/src/tutorial/cython_tu­torial.html
  50. Overcoming Python's GIL with Cython
    https://lbolla.info/python-threads-cython-gil
  51. GlobalInterpreterLock
    https://wiki.python.org/mo­in/GlobalInterpreterLock
  52. The Magic of RPython
    https://refi64.com/posts/the-magic-of-rpython.html
  53. RPython: Frequently Asked Questions
    http://rpython.readthedoc­s.io/en/latest/faq.html
  54. RPython’s documentation
    http://rpython.readthedoc­s.io/en/latest/index.html
  55. RPython (Wikipedia)
    https://en.wikipedia.org/wi­ki/PyPy#RPython
  56. Getting Started with RPython
    http://rpython.readthedoc­s.io/en/latest/getting-started.html
  57. PyPy (home page)
    https://pypy.org/
  58. PyPy (dokumentace)
    http://doc.pypy.org/en/latest/
  59. Localized Type Inference of Atomic Types in Python (2005)
    http://citeseer.ist.psu.e­du/viewdoc/summary?doi=10­.1.1.90.3231
  60. Tutorial: Writing an Interpreter with PyPy, Part 1
    https://morepypy.blogspot­.com/2011/04/tutorial-writing-interpreter-with-pypy.html
  61. List of numerical analysis software
    https://en.wikipedia.org/wi­ki/List_of_numerical_analy­sis_software
  62. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  63. 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/
  64. 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/
  65. PyPy is the Future of Python (článek z roku 2010)
    https://alexgaynor.net/2010/ma­y/15/pypy-future-python/
  66. Portal:Python programming
    https://en.wikipedia.org/wi­ki/Portal:Python_programming
  67. RPython Frontend and C Wrapper Generator
    http://www.codeforge.com/ar­ticle/383293
  68. PyPy’s Approach to Virtual Machine Construction
    https://bitbucket.org/pypy/ex­tradoc/raw/tip/talk/dls2006/py­py-vm-construction.pdf
  69. Tutorial: Writing an Interpreter with PyPy, Part 1
    https://morepypy.blogspot­.com/2011/04/tutorial-writing-interpreter-with-pypy.html
  70. A simple interpreter from scratch in Python (part 1)
    http://www.jayconrod.com/posts/37/a-simple-interpreter-from-scratch-in-python-part-1
  71. Brainfuck Interpreter in Python
    https://helloacm.com/brainfuck-interpreter-in-python/
  72. Interpretry, překladače, JIT překladače a transpřekladače programovacího jazyka Lua
    https://www.root.cz/clanky/interpretry-prekladace-jit-prekladace-a-transprekladace-programovaciho-jazyka-lua/
  73. LuaJIT – Just in Time překladač pro programovací jazyk Lua
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/
  74. LuaJIT – Just in Time překladač pro programovací jazyk Lua (2)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-2/
  75. Nuitka
    https://github.com/Nuitka/Nuitka
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.