Hlavní navigace

Rychlost CPythonu 3.11 a 3.12 v porovnání s JIT a AOT překladači Pythonu (2)

23. 11. 2023
Doba čtení: 28 minut

Sdílet

 Autor: Depositphotos
Na úvodní článek o testování rychlosti interpretrů Pythonu v porovnání s JIT a AOT překladači dnes navážeme. Ukážeme si, jak se projeví paralelizace kódu (Numba) a zjistíme vliv rychlosti I/O operací na celkovou rychlost benchmarků.

Obsah

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

2. Měříme skutečně rychlost výpočtů nebo rychlost zápisu výsledného rastrového obrázku?

3. Oddělení výpočtu od kódu pro uložení výsledku benchmarku

4. Vliv pomalosti I/O operací na rychlost běhu benchmarku

5. Grafy s časy výpočtů a I/O operací

6. Eliminace I/O operací

7. Paralelizace výsledného kódu JIT překladačem Numba

8. Paralelizace kódu nemusí být zcela triviálním úkolem

9. Výsledek běhu příkladu upraveného do paralelní podoby

10. Úprava výpočtu pro paralelní běh

11. Přidání typových informací pro Numbu a refaktoring

12. Benchmarky zaměřené na FP operace, optimalizace smyček, podmínek a na paralelizaci

13. Výsledky benchmarků

14. Výsledky omezené pouze na standardní interpretry Pythonu (CPython)

15. Vliv paralelizace na výsledky: Numba může překonat nativní céčkový kód

16. Krátké zamyšlení namísto závěru

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. Odkazy na Internetu

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

Mikrobenchmarky, kterými jsme se zabývali v úvodním článku sice měřily rychlost interpretrů Pythonu v porovnání s just in timeahead of time překladači, 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 dnes. A navíc musíme detailněji zjistit, jak vlastně interpretovat naměřená data – zda skutečně měříme rychlost výsledného kódu a/nebo rychlost I/O operací.

Obrázek 1: Rastrový obrázek generovaný benchmarky při nastavení rozlišení 400×400 pixelů a maximálního počtu iterací 255. Později budeme počet iterací zásadně zvětšovat, abychom omezili vliv pomalosti I/O operací na celkovou dobu trvání benchmarku.

2. Měříme skutečně rychlost výpočtů nebo rychlost zápisu výsledného rastrového obrázku?

V předchozím článku jsme měřili rychlost vykreslení Mandelbrotovy množiny Pythonním skriptem, který byl buď přímo spuštěn ve standardním interpretru CPythonu (verze 3.8 až 3.12), popř. byl přeložen JIT překladačem Numba nebo AOT překladačem mypyc. Výsledkem měření byl (mj.) i následující graf, v němž jsou porovnány rychlosti vykreslení Mandelbrotovy množiny do rastrových obrázků s rozlišením, které se pohybuje od 16×16 pixelů až po poměrně velké obrázky s rozlišením 4096×4096 pixelů:

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

Ještě předtím, než vyneseme konečný verdikt, za jakých okolností je vhodné použít nějakou technologii, je vhodné se zamyslet nad tím, co jsme vlastně změřili (to platí pro všechny benchmarky). Jedná se o tři operace:

  1. JIT překlad, popř. překlad bajtkódu (pro mypyc nulový čas)
  2. Čas výpočtu (programové smyčky, podmínky a FP operace)
  3. I/O operace (volání funkce print pro vykreslení bitmapy)

Na první pohled by se mohlo zdát, že doba I/O operací nebude hrát při dlouhotrvajících výpočtech žádnou zásadní roli, ovšem je lepší si vše ověřit (mohli bychom například předpokládat, že doba výpočtu barvy pixelu bude mnohem delší, než zápis samotné barvy, což je trojice celých čísel). Ideální by samozřejmě bylo využití profileru, to je však v případě mypyc a Numby relativně složitá operace (více příště). Proto se pokusíme o oddělení obou částí programu přímo ve zdrojovém kódu benchmarku.

Poznámka: všechny benchmarky produkují bitmapy ve formátu PPM (ASCII varianta), aby bylo možné snadno (například diffem) zjistit, zda jsou výsledky totožné. Bitmapa o rozlišení 16×16 pixelů má obecně následující formát (přičemž se do značné míry ignorují bílé znaky, včetně znaků pro konce řádků):
P3
16 16
255
224 224 224
224 224 224
224 224 224
224 224 224
216 216 216
216 216 216
216 216 216
216 216 216
232 232 232
232 232 232
216 216 216
...
...
...
208 208 208
208 208 208
208 208 208
216 216 216
216 216 216
216 216 216
216 216 216
216 216 216

3. Oddělení výpočtu od kódu pro uložení výsledku benchmarku

Připomeňme si, že původní tvar benchmarku vypadá následovně. Uvádíme zde variantu určenou pro JITování nástrojem Numba, takže ve zdrojovém kódu příkladu nalezneme dekorátor @jit s parametrem nopython=True, kterým se určuje, že se nemají volat Pythonní funkce, ale pouze funkce nativní. Z toho důvodu jsme nuceni použít zjednodušenou nativní variantu funkcí print a range (podtrženo):

#!/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 % 256][0]
            g = palette[i % 256][1]
            b = palette[i % 256][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)

Pokud se budeme snažit o zachování původního zdrojového kódu v co největší míře, můžeme provést rozdělení na výpočetní a exportní část například následujícím způsobem. Povšimněte si použití Numpy, což je ovšem zcela v pořádku, protože nástroj Numba je optimalizován právě pro dobrou a rychlou kooperaci s Numpy:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import palette_mandmap
from sys import argv, exit
 
import numpy as np
from numba import jit
 
 
@jit(nopython=True)
def calc_mandelbrot(width, height, maxiter, palette):
    iters = np.empty((height, width), dtype=np.uint8)
 
    # calc part
    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
            iters[y][x] = i
 
            cx += 3.0/width
        cy += 3.0/height
 
    # image export part
    print("P3")
    print(width)
    print(height)
    print("255")
    for y in range(0, height):
        for x in range(0, width):
            i = iters[y][x]
            r = palette[i % 256][0]
            g = palette[i % 256][1]
            b = palette[i % 256][2]
            print(r)
            print(g)
            print(b)
 
 
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)
Poznámka: velmi snadno je možné si ověřit, že výsledné soubory generované oběma variantami benchmarků budou produkovat naprosto totožné rastrové obrázky.

4. Vliv pomalosti I/O operací na rychlost běhu benchmarku

Nyní benchmark uvedený v předchozí kapitole budeme postupně modifikovat. Nejprve ho na testovacím počítači spustíme tak, jak byl napsán, samozřejmě s využitím JIT překladače nástroje Numba. Pro rastrové obrázky o rozlišení od 16×16 pixelů do 4096×4096 pixelů (a pro maximální počet iterací roven 255) dostaneme následující výsledky (první sloupec obsahuje velikost obrázku, druhý čas výpočtu v sekundách a třetí kapacitu alokované paměti z pohledu operačního systému – RSS):

16 3.45 271180
24 3.44 271444
32 4.27 271384
48 5.16 271136
64 5.13 271148
96 5.12 271184
128 5.07 271312
192 5.13 271304
256 5.13 271308
384 5.19 271268
512 5.25 271124
768 5.48 271316
1024 5.75 271316
1536 6.59 271184
2048 7.83 271008
3072 11.18 271296
4096 15.98 271316

Vidíme, že rastrový obrázek s rozlišením 4096×4096 byl spočten a uložen za cca šestnáct sekund.

Dále si zkusme zakomentovat poslední tři příkazy ve smyčce, v níž se rastrový obrázek ukládá:

    # image export part
    print("P3")
    print(width)
    print(height)
    print("255")
    for y in range(0, height):
        for x in range(0, width):
            i = iters[y][x]
            r = palette[i % 256][0]
            g = palette[i % 256][1]
            b = palette[i % 256][2]
            # print(r)
            # print(g)
            # print(b)

Nyní tedy budeme provádět celý výpočet, ovšem nebudeme vykreslovat jednotlivé pixely výsledného rastrového obrázku. Pokud nyní předpokládáte (stejně jako původně autor tohoto článku), že výsledné časy budou prakticky totožné, možná budete překvapeni:

16 2.93 266752
24 2.93 266812
32 2.93 266952
48 2.91 267072
64 3.66 266796
96 4.40 267060
128 4.37 266928
192 4.32 266944
256 4.34 266820
384 4.35 266948
512 4.35 266776
768 4.38 266816
1024 4.39 266688
1536 4.42 266820
2048 4.47 266500
3072 4.65 266932
4096 4.97 266804

Z výsledků je patrné, že samotný FP výpočet není zdaleka tou nejkritičtější operací. To má dalekosáhlé důsledky, ke kterým se ještě vrátíme.

Při třetím běhu naopak vynecháme výpočet a budeme ukládat prázdnou bitmapu (s černými pixely):

    # calc part
    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
            iters[y][x] = i
 
            cx += 3.0/width
        cy += 3.0/height

Nyní již dokážeme výsledek do určité míry predikovat – samotné uložení rastrového obrázku je tak náročná I/O operace, že bude nejenom měřitelná, ale překročí dobu výpočtů:

16 3.48 271076
24 3.48 270880
32 3.45 270948
48 4.49 271016
64 5.23 270892
96 5.18 271068
128 5.15 271076
192 5.17 271020
256 5.18 271032
384 5.25 270952
512 5.29 270880
768 5.47 271040
1024 5.77 271212
1536 6.53 270952
2048 7.79 270928
3072 10.92 270868
4096 15.37 270856

Jen pro zajímavost vynechme výpočet, ovšem uložme každý pixel dvakrát, čímž se počet I/O operací prakticky zdvojnásobí (výsledná bitmapa ovšem nebude korektní):

    # image export part
    print("P3")
    print(width)
    print(height)
    print("255")
    for y in range(0, height):
        for x in range(0, width):
            i = iters[y][x]
            r = palette[i % 256][0]
            g = palette[i % 256][1]
            b = palette[i % 256][2]
            print(r)
            print(g)
            print(b)
            print(r)
            print(g)
            print(b)

Časy nebudou dvojnásobné, ale to je již možné predikovat, protože víme, že samotné JITování kódu trvá cca 3–4 sekundy:

16 3.46 271212
24 3.44 271292
32 3.47 271320
48 3.45 271348
64 5.06 271456
96 5.13 271332
128 5.11 271336
192 5.12 271336
256 5.16 271208
384 5.25 271172
512 5.41 271308
768 5.81 271472
1024 6.37 271308
1536 8.35 271588
2048 10.43 271304
3072 16.79 271304
4096 25.89 271304

5. Grafy s časy výpočtů a I/O operací

Porovnání běhu benchmarků je pochopitelně výhodnější provést ve vizuální podobě, takže se vrátíme ke grafům. Na následujících grafech jsou zobrazeny časy všech čtyř variant téhož benchmarku popsaných v předchozí kapitole:

Označení Význam
normal celý benchmark s výpočtem i exportem
no output pouze výpočet
no calc pouze export (černého) rastrového obrázku
double output výpočet i export, ovšem každý pixel je zapsán dvakrát

Pro velmi malé rozměry obrázků bude největší roli hrát samotný JIT překladač:

Obrázek 3: Vliv JIT překladu na dobu běhu benchmarků.

Naopak pro velké rastrové obrázky vychází, že největší vliv mají I/O operace (tedy konkrétně funkce print) a nikoli vlastní výpočet, což je překvapující:

Obrázek 4: Vliv opakovaného volání funkce print na dobu běhu benchmarku.

A takto vypadá situace, kdy na horizontální osu vyneseme velikost výsledného obrázku a na osu vertikální dobu běhu benchmarku v sekundách. Jednotlivé naměřené hodnoty jsou proloženy lineárními úseky:

Obrázek 5: Rychlosti různých benchmarků pro rozdílné velikosti výsledných rastrových obrázků.

6. Eliminace I/O operací

V dnešním článku nás v prvé řadě zajímá rychlost provádění výpočtů a nikoli to, jak rychlá či naopak pomalá je implementace funkce print. Z tohoto důvodu se budeme snažit omezit vliv I/O operací na rozumné minimum. Využijeme přitom faktu, že u výpočtu Mandelbrotovy množiny lze ponechat relativně malou velikost výsledného rastrového obrázku a naopak zvýšit maximální počet iterací, což povede k razantnímu zvýšení počtu FP operací.

Konkrétně to bude v našem konkrétním případě bude znamenat, že rozlišení obrázku nastavíme na 256×256 pixelů, což je dostatečně velké rozlišení pro odhalení chyby („fast math“) ve výpočtu, ale počet I/O operací se oproti největšímu obrázku z předchozích benchmarků sníží na 1/256, což je méně než 1% (a proto bude jejich vliv malý). A počet iterací naopak vzroste až na 1000000, takže doby výpočtu budou v některých případech přesahovat půl hodiny.

Obrázek 6: Výsledek výpočtu Mandelbrotovy množiny při nastavení maximálního množství iterací na 100 000.

Obrázek 7: Výsledek výpočtu Mandelbrotovy množiny při nastavení maximálního množství iterací na 1 000 000.

7. Paralelizace výsledného kódu JIT překladačem Numba

Další optimalizace, kterou Numba dokáže zajistit, je paralelizace části kódu, typicky programové smyčky nebo častého volání nějaké funkce. Pro tento účel se používá parametr parallel=True předaný dekorátoru @jit a navíc je vhodné při generování indexů atd. použít namísto vestavěného generátoru range jeho paralelní variantu prange. Jak ale takový paralelní výpočet probíhá? Kód, resp. jednotlivé iterace nebo celé volání funkce, je spouštěn ve vláknech. Počet vláken lze nastavit; výchozí hodnota odpovídá počtu procesorových jader. Co to znamená v praxi? Testování probíhá na počítači se šestnácti jádry a tudíž prange rozdělí výpočet do šestnácti vláken, která běží samostatně (protože v nativní části nemáme žádný GIL!).

8. Paralelizace kódu nemusí být zcela triviálním úkolem

Mohlo by se zdát, že díky tomu, že nástroj Numba nabízí volbu parallel=True a navíc i „paralelní“ generátor prange bude paralelizace kódu zcela triviální. V některých případech je to samozřejmě pravda, ovšem platí to i pro náš benchmark? Můžeme se o tom snadno přesvědčit jeho úpravou:

  1. Před hlavičku funkce uvedeme @jit(nopython=True, parallel=True)
  2. Vnější výpočetní smyčka bude používat prange a nikoli range
  3. Smyčka pro uložení bitmapy nepoběží paralelně (je snad zřejmé proč)
  4. Přístup do pole iters lze provést z více vláken, pokud se nebude jednat o stejnou buňku

Vnější smyčka se tedy změní takto:

    # calc part
    cy = -1.5
    for y in prange(0, height):
        ...
        ...
        ...
        cy += 3.0/height

Upravený kód demonstračního příkladu bude vypadat následovně:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import palette_mandmap
from sys import argv, exit
 
import numpy as np
from numba import jit, prange
 
 
@jit(nopython=True, parallel=True)
def calc_mandelbrot(width, height, maxiter, palette):
    iters = np.empty((height, width), dtype=np.uint8)
 
    # calc part
    cy = -1.5
    for y in prange(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
            iters[y][x] = i
 
            cx += 3.0/width
        cy += 3.0/height
 
    # image export part
    print("P3")
    print(width)
    print(height)
    print("255")
    for y in range(0, height):
        for x in range(0, width):
            i = iters[y][x]
            r = palette[i % 256][0]
            g = palette[i % 256][1]
            b = palette[i % 256][2]
            print(r)
            print(g)
            print(b)
 
 
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)

9. Výsledek běhu příkladu upraveného do paralelní podoby

Podívejme se nyní na výsledný obrázek, který získáme po spuštění benchmarku z předchozí kapitoly:

Obrázek 8: Výsledek „paralelizovaného“ benchmarku.

Z výsledků je jasně patrné, že se v žádném případě nejedná o očekávaný obrázek Mandelbrotovy množiny. Namísto toho vidíme šestnáct kopií části množiny začínající na nulové imaginární souřadnici (cy=0). Tento obrázek nám současně poměrně přesně napovídá, co se stalo:

  • Numba skutečně výpočet rozdělila do šestnácti vláken (na mém počítači se šestnácti jádry – jinde to může být odlišné)
  • Ovšem s lokální proměnnou cy se nepracuje korektně – je v každém vláknu nastavena na nulu a poté „lokálně“ zvyšována, takže každé vlákno vlastně počítá stejnou část množiny

K lokální skalární proměnné není možné tímto způsobem přistupovat z většího množství vláken. Proto je nutné výpočet vhodným způsobem upravit, což si ukážeme v navazující kapitole.

10. Úprava výpočtu pro paralelní běh

Ve skutečnosti je úprava výpočetní části benchmarku do takové podoby, která umožní paralelní výpočet, poměrně triviální (benchmark založený na Mandelbrotově množině je skutečně v tomto ohledu dosti univerzální). Postačuje nám, aby se imaginární hodnota komplexního čísla c (tedy lokální proměnná cy) explicitně vypočítala uvnitř paralelizované vnější smyčky. To znamená, že namísto:

    # calc part
    cy = -1.5
    for y in prange(0, height):
        cx = -2.0
        for x in range(0, width):
        ...
        ...
        ...
            cx += 3.0/width
        cy += 3.0/height

…přesuneme proměnnou cy dovnitř smyčky, což znamená, že každé vlákno bude vlastnit „svoji“ proměnnou cy nezávislou na ostatních vláknech:

    # calc part
    for y in prange(0, height):
        cy = -1.5 + 3.0*y/height
        cx = -2.0
        for x in range(0, width):
            ...
            ...
            ...
 
            cx += 3.0/width
Poznámka: zopakujme si, že souběžný přístup k poli ndarray není z tohoto pohledu problematický.

11. Přidání typových informací pro Numba a refaktoring

I JIT překladač implementovaný v Numbě dokáže zpracovat informace o typech parametrů překládané funkce. Ovšem tyto typové informace nejsou zapisovány dnes standardním způsobem (type hints), ale přímo v dekorátoru @jit nebo @njit. V našem konkrétním případě, tj. pro funkci určenou pro vykreslení Mandelbrotovy množiny, se předává několik parametrů:

  • šířka obrázku – celé číslo
  • výška obrázku – celé číslo
  • maximální počet iterací – celé číslo
  • barvová paleta – 256 trojic celých čísel (což je UniTuple s 256 vnořenými UniTuple)

V tomto konkrétním případě bude informace o typech vypadat takto:

@jit((int64, int64, int64, UniTuple(UniTuple(int64, 3), 256)), nopython=True, parallel=True)
def calc_mandelbrot(width, height, maxiter, palette):
    ...
    ...
    ...

Další úprava spočívá v refaktoringu – ze zdrojového kódu „vytáhneme“ funkci pro výpočet počtu iterací pro jediný bod v komplexní rovině. Tato funkce bude plně přeložena do nativního kódu a dokonce bude vložena přímo do kódu (bez jejího volání). Tato úprava opět (kupodivu) pomůže JIT překladači, zde konkrétně při rozbalování smyčky:

@jit(nopython=True)
def calc_pixel(maxiter, cx, cy):
    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
    return i

Výsledný zdrojový kód bude po všech úpravách vypadat následovně:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import palette_mandmap
from sys import argv, exit
 
import numpy as np
from numba import jit, prange
from numba.types import UniTuple, int64
 
 
@jit(nopython=True)
def calc_pixel(maxiter, cx, cy):
    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
    return i
 
 
@jit((int64, int64, int64, UniTuple(UniTuple(int64, 3), 256)), nopython=True, parallel=True)
def calc_mandelbrot(width, height, maxiter, palette):
    iters = np.empty((height, width), dtype=np.uint8)
 
    # calc part
    for y in prange(0, height):
        cy = -1.5 + 3.0*y/height
        cx = -2.0
        for x in range(0, width):
            i = calc_pixel(maxiter, cx, cy)
            iters[y][x] = i
 
            cx += 3.0/width
 
    # image export part
    print("P3")
    print(width)
    print(height)
    print("255")
    for y in range(0, height):
        for x in range(0, width):
            i = iters[y][x]
            r = palette[i % 256][0]
            g = palette[i % 256][1]
            b = palette[i % 256][2]
            print(r)
            print(g)
            print(b)
 
 
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)
    # calc_mandelbrot.parallel_diagnostics(level=4)
iterations="1 10 100 1000 10000 100000 1000000"
 
OUTFILE="numba7.times"
PREFIX="numba7"
 
rm $OUTFILE
 
for iter in $iterations
do
    echo $iter
    echo -n "$iter " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" python3 mandelbrot_python.py 256 256 $iter 255 > "${PREFIX}_${iter}.ppm"
done

12. Benchmarky zaměřené na FP operace, optimalizace smyček, podmínek a na paralelizaci

Podobně jako minule máme i dnes 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)
  8. Numba: varianta používající n-rozměrné pole (ndarray)
  9. Numba: nekorektní „paralelní“ varianta
  10. Numba: korektní „paralelní“ varianta

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)
numba 5 Numba: varianta používající n-rozměrné pole (ndarray)
numba 6 Numba: nekorektní „paralelní“ varianta
numba 7 Numba: korektní „paralelní“ varianta
Poznámka: optimalizovaný nativní kód opět 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).

13. Výsledky benchmarků

Na druhém grafu jsou zobrazeny časy běhu benchmarků pro malý počet iterací, což znamená i relativně malý počet FP operací, které se musí provést. 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 9: Časy běhu benchmarků pro malý počet iterací. Zde se nejvíce projeví čas inicializace procesu a popř. JITování kódu.

Pro větší počet iterací 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! A navíc (což zde příliš dobře nevidíme) bude „paralelní“ výpočet realizovaný Numbou rychlejší, než nativní kód (viz další text):

Obrázek 10: Časy běhu benchmarků pro velký počet iterací. Zde se nejvíce projeví čas inicializace procesu a popř. JITování kódu

Poznámka: časy jsou stále vyneseny v sekundách! Na takovém časovém měřítku se již I/O operace prakticky vůbec neprojeví.

Třetí 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 maximální počty iterací, na vertikální osu pak doby běhu benchmarku v sekundách:

Obrázek 11: Časy běhu všech benchmarků v závislosti na požadovaném maximálním počtu iterací.

14. Výsledky omezené pouze na standardní interpretry Pythonu (CPython)

V úvodním č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 12: Porovnání rychlostí interpretrů Pythonu (CPython 3.x).

Poznámka: opět platí, že Python 3.11 i Python 3.12 jsou skutečně významně rychlejší, než předchozí verze interpretrů, což je zajisté dobrá zpráva i důvod pro přechod na novou verzi na produkčních systémech.

15. Vliv paralelizace na výsledky: Numba může překonat nativní céčkový kód

Hlavním důvodem, proč vlastně vznikl tento doplňkový článek, je zjištění, zda a jak vůbec se projeví (polo)automatická paralelizace kódu prováděná nástrojem Numba v případě, že se použije dekorátor @jit(parallel=True) a taktéž v případě použití funkce prange namísto standardní funkce range. Podívejme se tedy na výsledky, které porovnávají časy výpočtů pro JITovaný neparalelizovaný kód (numba5) a pro kód plně paralelizovaný (numba7). Z grafu je patrné více než dvojnásobné zrychlení pro velký počet iterací. Povšimněte si, že zrychlení není šestnáctinásobné, ovšem musíme si uvědomit, že cca 4–5 sekund trvá JITování kódu. To znamená, že poměr není 32:14, ale spíše 28:10, takže reálné zrychlení výpočtů je přibližně trojnásobné:

Obrázek 13: Neparalelizovaný (numba5) vs paralelizovaný (numba7) kód.

Poznámka: to ovšem znamená, že další přidávání vláken (a CPU jader) už pravděpodobně nebude mít valného významu – viz též slavný Amdahlův zákon, který možná začíná být tím hlavním zákonem v IT.

Nejzajímavější výsledek si necháváme na konec. Jedná se o porovnání rychlosti výpočtů prováděné nativním kódem (který pracuje v jediném vláknu) s paralelizovaným kódem, jenž je výsledkem JITování nástrojem Numba. Ukazuje se, že v tomto případě dostaneme lepší výsledek v případě použití Pythonu oproti nativnímu kódu (a to i když překladač C použije všechny nejmodernější optimalizace)!

Obrázek 13: Nativní kód vs. kód psaný v Pythonu, jenž je JITován a paralelizován nástrojem Numba.

Poznámka: mohlo by se asi zdát, že porovnání není férové (a v mnoha ohledech skutečně férové není), ovšem paralelizace Numbou je provedena z pohledu vývojáře prakticky zadarmo, zatímco výpočet ve více vláknech v C lze realizovat mnohem složitěji.

16. Krátké zamyšlení namísto závěru

Je zajímavé, do jaké míry dokážou nástroje mypyc a ještě více nástroj Numba poměrně kvalitně překládat zdrojové kódy Pythonu v případě, že jim jsou poskytnuty typové informace (a jak špatný je výsledek bez nich). V tom spočívá jedna část úspěchu těchto nástrojů. Druhá část spočívá v tom, že nám umožňuje označit ty funkce, které lze přeložit do nativního kódu bez toho, aby se v nich interně používal (ne)slavný GIL. Pravděpodobně není jen náhoda, že se jedná o stejné strategie, jaké jsou použity v nástroji CPython, kterým jsme se zabývali v článku Praktické použití nástroje Cython při překladu Pythonu do nativního kódu – i zde se v první řadě jednalo o zápis typových informací, odstranění volání Pythonovských funkcí (původní print) a tím pádem i možnost ignorování GILu. Pravděpodobně zde tedy vede cesta (nezávisle na tom, zda se jedná o JIT nebo AOT) k rychlejšímu Pythonu (pokud tedy vynecháme nekonečné diskuse o odstranění GILu přímo z CPythonu).

root_podpora

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 deset 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ších šest verzí jsou určeny pro použití společně s JIT překladačem Numba (přičemž dvě verze umožní paralelní běh ve více vláknech). 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 https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v1/
2 mandelbrot-v2 použití anotace @jit 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 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 úprava předchozího příkladu: rozdělení části provádějící výpočet od části provádějící export obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v5/
6 mandelbrot-v6 úprava příkladu tak, aby byl umožněn jeho běh ve více vláknech (s nekorektním výsledkem) https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v6/
7 mandelbrot-v7 přidání typových informací pro zlepšení rychlosti paralelního běhu benchmarku https://github.com/tisnik/most-popular-python-libs/blob/master/numba/mandelbrot-v7/
       
8 mypyc/mandelbrot5 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
9 mypyc/mandelbrot6 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
       
10 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. 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. LuaJIT – Just in Time překladač pro programovací jazyk Lua (3)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-3/
  76. LuaJIT – Just in Time překladač pro programovací jazyk Lua (4)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-4/
  77. LuaJIT – Just in Time překladač pro programovací jazyk Lua (5 – tabulky a pole)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-5-tabulky-a-pole/
  78. LuaJIT – Just in Time překladač pro programovací jazyk Lua (6 – překlad programových smyček do mezijazyka LuaJITu)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-6-preklad-programovych-smycek-do-mezijazyka-luajitu/
  79. LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-7-dokonceni-popisu-mezijazyka-luajitu/
  80. LuaJIT – Just in Time překladač pro programovací jazyk Lua (8 – základní vlastnosti trasovacího JITu)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-8-zakladni-vlastnosti-trasovaciho-jitu/
  81. LuaJIT – Just in Time překladač pro programovací jazyk Lua (9 – další vlastnosti trasovacího JITu)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-9-dalsi-vlastnosti-trasovaciho-jitu/
  82. LuaJIT – Just in Time překladač pro programovací jazyk Lua (10 – JIT překlad do nativního kódu)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-10-jit-preklad-do-nativniho-kodu/
  83. LuaJIT – Just in Time překladač pro programovací jazyk Lua (11 – JIT překlad do nativního kódu procesorů s architekturami x86 a ARM)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-11-jit-preklad-do-nativniho-kodu-procesoru-s-architekturami-x86-a-arm/
  84. LuaJIT – Just in Time překladač pro programovací jazyk Lua (12 – překlad operací s reálnými čísly)
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-12-preklad-operaci-s-realnymi-cisly/
  85. Podpora SIMD (vektorových) instrukcí na RISCových procesorech
    https://www.root.cz/clanky/podpora-simd-vektorovych-instrukci-na-riscovych-procesorech/
  86. Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky technologie
    https://www.root.cz/clanky/uzitecne-rozsireni-gcc-podpora-simd-vektorovych-instrukci-nedostatky-technologie/
  87. Podpora SIMD operací v GCC s využitím intrinsic pro nízkoúrovňové optimalizace
    https://www.root.cz/clanky/podpora-simd-operaci-v-gcc-s-vyuzitim-intrinsic-pro-nizkourovnove-optimalizace/
  88. Podpora SIMD operací v GCC s využitím intrinsic: technologie SSE
    https://www.root.cz/clanky/podpora-simd-operaci-v-gcc-s-vyuzitim-intrinsic-technologie-sse/
  89. Rozšíření instrukční sady „Advanced Vector Extensions“ na platformě x86–64
    https://www.root.cz/clanky/rozsireni-instrukcni-sady-advanced-vector-extensions-na-platforme-x86–64/
  90. Rozšíření instrukční sady F16C, FMA a AVX-512 na platformě x86–64
    https://www.root.cz/clanky/rozsireni-instrukcni-sady-f16c-fma-a-avx-512-na-platforme-x86–64/
  91. Použití instrukcí SSE a AVX pro zrychlení bitových operací
    https://www.root.cz/clanky/pouziti-instrukci-sse-a-avx-pro-zrychleni-bitovych-operaci/
  92. Rozšíření instrukční sady AVX-512 na platformě x86–64 (dokončení)
    https://www.root.cz/clanky/rozsireni-instrukcni-sady-avx-512-na-platforme-x86–64-dokonceni/
  93. Nuitka
    https://github.com/Nuitka/Nuitka
  94. SIMD Autovectorization in Numba
    https://tbetcke.github.io/hpc_lec­ture_notes/simd.html

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

Autor článku

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