Nástroj Cython a typové anotace podporované Pythonem

21. 5. 2024
Doba čtení: 31 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Cython je AOT překladač Pythonu, který nově podporuje jak původní syntaxi nástrojů Cython/Pyrex, tak i zápis s dekorátory a typovými informacemi kompatibilní s Pythonem. Právě touto problematikou se budeme zabývat.

Obsah

1. Nástroj Cython a typové anotace

2. Typové anotace v Pythonu

3. Ukázky zápisu typových anotací

4. Duck typing v Pythonu

5. Překlad funkce s duck typingem do C Cythonem

6. Zajištění syntaxe kompatibilní s Pythonem

7. Přidání informace o typech parametrů funkce pro součet celých čísel

8. Přepis funkce s typy parametrů do podoby kompatibilní s Pythonem

9. Přidání informace o návratovém typu funkce pro součet celých čísel

10. Přepis funkce s plnými typovými informacemi do podoby kompatibilní s Pythonem

11. Zákaz použití GILu v překládané funkci

12. Volání funkcí ze standardní knihovny jazyka C

13. Základní varianta benchmarku naprogramovaná v čistém Pythonu

14. Přepis benchmarku do Cythonu

15. Varianta se syntaxí kompatibilní s Pythonem

16. Pole a předávání ukazatelů

17. Opětovná úprava se syntaxí kompatibilní s Pythonem

18. Výsledek transpilace Cythonem do céčka

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

20. Odkazy na Internetu

1. Nástroj Cython a typové anotace

V článcích RPython vs Cython aneb dvojí přístup k překladu Pythonu do nativního kódu a Praktické použití nástroje Cython při překladu Pythonu do nativního kódu jsme se seznámili se základními vlastnostmi nástroje nazvaného Cython (pozor – nikoli CPython, což je označení standardního interpretru Pythonu naprogramovaného v céčku). Cython patří mezi takzvané AOT (Ahead Of Time) překladače Pythonu, což v tomto případě konkrétně znamená, že zdrojové kódy projektu napsaného v Pythonu jsou transformovány do céčkovského kódu a následně přeloženy standardním céčkovským překladačem (například GCC apod.) a poté slinkovány do výsledného spustitelného nativního souboru.

Obrázek 1: Logo Cythonu.

Na první pohled by se mohlo zdát, že výsledkem budou mnohem rychlejší aplikace, protože zcela odpadne fáze interpretace. Ve chvíli, kdy Cython nezná datový typ funkce/proměnné/argumentu, použije PyObject *, tedy ukazatel na datovou strukturu reprezentující v Pythonu libovolnou hodnotu. V dalším kódu je samozřejmě nutné z tohoto objektu získat skutečnou hodnotu. I takto vlastně velmi primitivně provedený překlad dokáže programový kód do určité míry zrychlit, což ostatně uvidíme i ve výsledku benchmarků. Cython jde ale ještě dále, protože rozšiřuje jazyk Python o další klíčová slova, především pak o slovo cdef. Toto klíčové slovo je možné použít pro přesnou specifikaci typu proměnné či argumentu, a to způsobem, který plně vychází z programovacího jazyka C. Tato typová informace samozřejmě umožňuje provedení dalších optimalizací ve výsledném kódu (opět uvidíme na výsledcích benchmarku).

Obrázek 2: Pro porovnání – logo standardního Pythonu.

Problém spočívá v tom, že původní zápis založený na cdef a „céčkovsky“ zapsaných datových typech je nekompatibilní se syntaxí standardního Pythonu. Tento problém je ovšem v současnosti do značné míry vyřešen, protože nyní Cython může používat typové anotace. Kombinaci těchto dvou technologií je věnován dnešní článek.

2. Typové anotace v Pythonu

Do syntaxe a sémantiky programovacího jazyka Python se postupně přidává a vylepšuje, resp. přesněji řečeno zjednodušuje a zpřesňuje podpora pro specifikaci takzvaných typových anotací, resp. typových nápověd (type annotations, type hints). V současné podobě se jedná o nová syntaktická pravidla, která v zápisu zdrojového kódu umožňují nepovinnou a do značné míry zcela dobrovolnou specifikaci typů parametrů funkcí a metod, typů návratových hodnot funkcí a metod, typů globálních i lokálních proměnných, typových parametrů u generických datových typů atd. Programovací jazyk Python sice umožňuje zápis typových anotací, ale (alespoň prozatím) neprovádí jejich kontrolu a vlastně je ani nijak nevyužívá pro běh aplikací. Tuto činnost ponechává na jiných nástrojích, které jsou typicky založeny na statické typové analýze nebo (jak uvidíme dále) pro podporu AOT (ahead of time) překladu s optimalizacemi.

Poznámka: pochopitelně ovšem stále platí, že statická typová analýza, popř. statická typová kontrola nebude a ani nemůže být v případě programovacího jazyka Python stoprocentně úspěšná, což je vlastnost, kterou si ukážeme na několika demonstračních příkladech uvedených v navazujících kapitolách.

Zavedení typových anotací do programovacího jazyka Python bylo, zejména zpočátku, provázeno určitým odporem částí vývojářů a někdy taktéž obavou o to, jestli se z původně poměrně jednoduchého a snadno naučitelného Pythonu nestává zcela odlišný programovací jazyk (z jazyka snadno naučitelného i lidmi, kteří nejsou profesionálními programátory na komplikovaný jazyk určený výhradně pro profesionály). Ovšem na tomto místě je nutné zdůraznit, že se jedná o zcela nepovinnou součást jazyka, která je vlastně do značné míry samotným Pythonem (přesněji řečeno standardním interpretrem Pythonu neboli CPythonem) ignorována.

Nástroje typu Mypy, Pyright, Cython či LSP servery, které typové informace zpracovávají a vyžadují pro svou činnost, pracují zcela nezávisle na standardním interpretru Pythonu (popř. je možná jejich integrace do vývojových prostředí). Na druhou stranu, i přes výše uvedené rozpaky, právě typové anotace a jejich kontrola (a nutno dodat: i zpracování integrovanými vývojovými prostředími) do značné míry usnadňují vývoj rozsáhlejších aplikací. Ostatně můžeme zde vidět souvislost s dvojicí JavaScript:TypeScript. A kvalita optimalizací prováděná nástrojem Cython do značné míry závisí právě na poskytnutých typových informacích.

3. Ukázky zápisu typových anotací

Typovými anotacemi v Pythonu jsme se již zabývali v článcích [1] [2] [3]. Proto si dnes pouze bez podrobnějších vysvětlení ukažme, jakým způsobem se typové informace zapisují. Stejný zápis, i když mnohdy s odlišnými typy, budeme realizovat i v Cythonu.

Definice funkce pro součet dvou celých čísel (s neomezeným rozsahem). Specifikovány jsou typy parametrů i typ návratové hodnoty:

def add(a:int, b:int) -> int:
    return a+b

Definice funkce určené pro spojení dvou seznamů, přičemž prvky těchto seznamů jsou množiny celých čísel. Opět je specifikován jak typ parametrů funkce tak i typ návratové hodnoty:

def append(a:list[set[int]], b:list[set[int]]) -> list[set[int]]:
    return a+b
Poznámka: těla obou předchozích funkcí jsou totožná, ovšem jejich sémantika je zcela odlišná.

Definice proměnné s typem n-tice. Každý prvek n-tice může mít specifikován vlastní typ (na rozdíl od seznamů):

p: tuple[int, float, bool, str] = (2.0, 3.14, 1, "Hello")

Definice proměnné typu slovník, se specifikací typu klíče i povolených hodnot:

d:dict[str, Union[int, float, str]] = {}
 
d["foo"] = 1
d["bar"] = 3.14
d[10] = 10
d[42] = "answer"

V novějších verzích Pythonu lze též psát:

d:dict[str, int | float | str] = {}
 
d["foo"] = 1
d["bar"] = 3.14
d[10] = 10
d[42] = "answer"

Definice generické funkce, u níž je zaručeno, že ve výsledné n-tici bude mít první prvek typ shodný s prvním parametrem a druhý prvek bude mít typ shodný s parametrem druhým:

def pair[T, U](first: T, second: U) -> tuple[T, U]:
    x = (first, second)
    return x
 
 
print(pair("A", "B"))

Poslední příklad ukazuje definici generické třídy, u níž je typově bezpečně určen typ ukládaných a načítaných prvků:

from typing import Generic, TypeVar
 
T = TypeVar('T')
 
 
class Collection(Generic[T]):
    def __init__(self) -> None:
        self.collection : list[T] = []
 
    def append(self, item: T) -> None:
        self.collection.append(item)
 
    def get_all(self) -> list[T]:
        return self.collection
 
 
c = Collection[int]()
c.append(1)
c.append(2)
print(c.get_all())

4. Duck typing v Pythonu

Jednou z typických a důležitých vlastností Pythonu je duck typing, který nám umožňuje zapsat například následující funkci:

def add_two_numbers(x, y):
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

Ve skutečnosti tato funkce, i přes své jméno, nesčítá pouze dvě celočíselné hodnoty, ale jakékoli Pythonovské objekty, pro které je definován operátor +. To si ostatně můžeme velmi snadno ověřit:

def add_two_numbers(x, y):
    return x + y
 
 
class Foo:
    def __init__(self, value):
        self._value = value
 
    def __add__(self, other):
        return Foo(self._value + other._value)
 
    def __str__(self):
        return "*" * self._value
 
 
def test_adding():
    f1 = Foo(1)
    f2 = Foo(2)
 
    print(add_two_numbers(123, 456))
    print(add_two_numbers("foo", "bar"))
    print(add_two_numbers([1,2,3], [4,5,6]))
    print(add_two_numbers((1,2,3), (4,5,6)))
    print(add_two_numbers(f1, f2))
 
 
test_adding()

Samotná funkce add_two_numbers je v bajtkódu reprezentována čtyřmi instrukcemi (zcela bez informace o typech):

  4           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE

Tuto funkci lze volat s různými parametry:

; *****************************************
; předání dvou celých čísel
; *****************************************
 22          16 LOAD_GLOBAL              1 (add_two_numbers)
             18 LOAD_CONST               3 (123)
             20 LOAD_CONST               4 (456)
             22 CALL_FUNCTION            2
             24 POP_TOP
 
 
 
; *****************************************
; předání dvou řetězců
; *****************************************
 23          26 LOAD_GLOBAL              1 (add_two_numbers)
             28 LOAD_CONST               5 ('foo')
             30 LOAD_CONST               6 ('bar')
             32 CALL_FUNCTION            2
             34 POP_TOP
 
 
 
; *****************************************
; předání dvou seznamů
; *****************************************
 24          36 LOAD_GLOBAL              1 (add_two_numbers)
             38 LOAD_CONST               1 (1)
             40 LOAD_CONST               2 (2)
             42 LOAD_CONST               7 (3)
             44 BUILD_LIST               3
             46 LOAD_CONST               8 (4)
             48 LOAD_CONST               9 (5)
             50 LOAD_CONST              10 (6)
             52 BUILD_LIST               3
             54 CALL_FUNCTION            2
             56 POP_TOP
 
 
 
; *****************************************
; předání dvou n-tic
; *****************************************
 25          58 LOAD_GLOBAL              1 (add_two_numbers)
             60 LOAD_CONST              11 ((1, 2, 3))
             62 LOAD_CONST              12 ((4, 5, 6))
             64 CALL_FUNCTION            2
             66 POP_TOP
 
 
 
; *****************************************
; předání dvou instancí třídy Foo
; *****************************************
 19           0 LOAD_GLOBAL              0 (Foo)
              2 LOAD_CONST               1 (1)
              4 CALL_FUNCTION            1
              6 STORE_FAST               0 (f1)
 
 20           8 LOAD_GLOBAL              0 (Foo)
             10 LOAD_CONST               2 (2)
             12 CALL_FUNCTION            1
             14 STORE_FAST               1 (f2)
 
 26          68 LOAD_GLOBAL              1 (add_two_numbers)
             70 LOAD_FAST                0 (f1)
             72 LOAD_FAST                1 (f2)
             74 CALL_FUNCTION            2
             76 POP_TOP
             78 LOAD_CONST               0 (None)
             80 RETURN_VALUE

Obrázek 3: Část HTML souboru „add_numbers1.html“ vytvořeného Cythonem. Žlutě označené řádky označují ty bloky kódu, o nichž Cython nemá typové informace a které musel přeložit dosti neefektivním způsobem simulujícím chování interpretru Pythonu.

5. Překlad funkce s duck typingem do C Cythonem

Pokusme se nyní přeložit funkci pro součet dvou hodnot z Cythonu do C a posléze do nativního kódu. Použijeme původní syntaxi Cythonu (resp. Pyrexu) a definici funkce, která se má přeložit do nativního kódu, začneme klíčovým slovem cdef a nikoli pouze def:

cdef add_two_numbers(x, y):
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

Tato funkce bude Cythonem přeložena do poměrně komplikované céčkovské funkce:

static PyObject *__pyx_f_13add_numbers_2_add_two_numbers(PyObject *__pyx_v_x, PyObject *__pyx_v_y) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  PyObject *__pyx_t_1 = NULL;
  int __pyx_lineno = 0;
  const char *__pyx_filename = NULL;
  int __pyx_clineno = 0;
  __Pyx_RefNannySetupContext("add_two_numbers", 1);
 
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = PyNumber_Add(__pyx_v_x, __pyx_v_y); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 2, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_r = __pyx_t_1;
  __pyx_t_1 = 0;
  goto __pyx_L0;
 
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_AddTraceback("add_numbers_2.add_two_numbers", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = 0;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
Poznámka: povšimněte si, že se zde pracuje s hodnotami typu PyObject, které se referencují a dereferencují. Navíc je vlastní součet realizován nepřímo funkcí PyNumber_Add. Je tomu tak z toho důvodu, aby byl stále zajištěn duck typing. Výsledek tedy v žádném případě nebude příliš rychlý ani paměťově efektivní.

Obrázek 4: Část HTML souboru „add_numbers2.html“ vytvořeného Cythonem. Žlutě označené řádky opět označují ty bloky kódu, o nichž Cython nemá typové informace a které musel přeložit dosti neefektivním způsobem simulujícím chování interpretru Pythonu.

6. Zajištění syntaxe kompatibilní s Pythonem

Předchozí zdrojový kód nebyl kvůli cdef kompatibilní s Pythonem; proto má ostatně příponu „.pyx“ a nikoli pouze „.py“. Ovšem novější verze Cythonu již podporují zápis, který je čistě Pythonovským kódem. Namísto cdef se v tomto případě použije standardní definice funkce s def, ovšem funkce je označena dekorátorem cfunc:

import cython
 
@cython.cfunc
def add_two_numbers(x, y):
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

7. Přidání informace o typech parametrů funkce pro součet celých čísel

První důležitou úpravou a vlastně i optimalizací, kterou můžeme provést, je přidání informace o typech parametrů funkce. Zápis je v tomto případě naprosto stejný, jako v klasickém céčku, tj. používá se pořadí datový_typ jméno_parametru. V našem případě budeme vyžadovat, aby parametry byly typu celé číslo, tj. v céčku se jedná o základní typ pojmenovaný int. Změněná funkce bude vypadat následovně:

cdef add_two_numbers(int x, int y):
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

Překlad neboli transpilace do céčka dopadne následovně:

static PyObject *__pyx_f_13add_numbers_3_add_two_numbers(int __pyx_v_x, int __pyx_v_y) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  PyObject *__pyx_t_1 = NULL;
  int __pyx_lineno = 0;
  const char *__pyx_filename = NULL;
  int __pyx_clineno = 0;
  __Pyx_RefNannySetupContext("add_two_numbers", 1);
 
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = __Pyx_PyInt_From_int((__pyx_v_x + __pyx_v_y)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 2, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_r = __pyx_t_1;
  __pyx_t_1 = 0;
  goto __pyx_L0;
 
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_AddTraceback("add_numbers_3.add_two_numbers", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = 0;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

Zvýrazněme si rozdíly mezi způsobem překladu původní funkce bez typových informací a funkce s informacemi o typech parametrů. Rozdíl spočívá v jediném řádku (a pochopitelně v odlišné hlavičce funkce – akceptuje odlišné typy parametrů):

  __pyx_t_1 = PyNumber_Add(__pyx_v_x, __pyx_v_y); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 2, __pyx_L1_error)
 
  __pyx_t_1 = __Pyx_PyInt_From_int((__pyx_v_x + __pyx_v_y)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 2, __pyx_L1_error)

Ve druhém případě se tedy sice provádí běžný součet nativním operátorem + (a tedy strojovou instrukcí add), ovšem výsledek se transformuje zpět na hodnotu typu PyObject *, tedy na obecnou hodnotu Pythonu podléhající duck typingu.

Obrázek 5: Část HTML souboru „add_numbers3.html“ vytvořeného Cythonem.

8. Přepis funkce s typy parametrů do podoby kompatibilní s Pythonem

Opět se podívejme na to, jakým způsobem je možné funkci z předchozí kapitoly upravit do takové podoby, aby byla její syntaxe kompatibilní s Pythonem. Opět tedy využijeme dekorátor @cfunc a navíc uvedeme typové informace o parametrech funkce. Tentokrát je ovšem zapíšeme nikoli „céčkovským“ stylem, ale tak, aby se jednalo o standardní type hinty:

import cython
 
@cython.cfunc
def add_two_numbers(x: cython.int, y: cython.int):
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

Povšimněte si, že nepoužíváme typ int (ten má totiž v Pythonu odlišný význam – celočíselná hodnota bez omezení rozsahu), ale typy importované přímo z Cythonu a plně odpovídající céčkovským protějškům:

Cython/Pyrex Zápis v Pythonu
bint cython.bint
char cython.char
signed char cython.schar
unsigned char cython.uchar
short cython.short
unsigned short cython.ushort
int cython.int
unsigned int cython.uint
long cython.long
unsigned long cython.ulong
long long cython.longlong
unsigned long longcython.ulonglong
float cython.float
double cython.double
long double cython.longdouble
float complex cython.floatcomplex
double complex cython.doublecomplex
long double complex cython.longdoublecomplex
size_t cython.size_t
Py_ssize_t cython.Py_ssize_t
Py_hash_t cython.Py_hash_t
Py_UCS4 cython.Py_UCS4
Poznámka: tato funkce bude přeložena do totožného céčkovského kódu, jako funkce z předchozí kapitoly.

9. Přidání informace o návratovém typu funkce pro součet celých čísel

Další úprava naší funkce se již přímo nabízí – musíme totiž navíc specifikovat i typ návratové hodnoty. Pokud totiž tento typ není zadán, považuje Cython za nutné vracet PyObject *, aby byly výsledky kompatibilní s běžnými pythonovskými funkcemi a metodami. Specifikace návratové hodnoty se opět provádí stejným způsobem, jako v programovacím jazyku C, tj. zápisem příslušného typu před jméno funkce:

cdef int add_two_numbers(int x, int y):
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

Z podoby výsledné céčkovské funkce je patrné, že jsme se přiblížili deklaraci, kterou by mohl zapsat programátor v céčku, přičemž přebytečné závorky, nadbytečný příkaz goto atd. odstraní každý moderní céčkovský překladač:

static int __pyx_f_13add_numbers_4_add_two_numbers(int __pyx_v_x, int __pyx_v_y) {
  int __pyx_r;
 
  __pyx_r = (__pyx_v_x + __pyx_v_y);
  goto __pyx_L0;
 
  /* function exit code */
  __pyx_L0:;
  return __pyx_r;
}

10. Přepis funkce s plnými typovými informacemi do podoby kompatibilní s Pythonem

Opět si ukažme, jakým způsobem by bylo možné přepsat funkci s uvedením plných typových informací ze syntaxe Cythonu (Pyrexu) do syntaxe plně kompatibilní s moderním Pythonem, který podporuje zápis typových informací (hintů). Jak se zapisují typy parametrů funkce již víme, takže pouze doplníme informaci o návratovém typu funkce, která se zapisuje mezi šipku → a dvojtečku označující začátek bloku příkazů uvnitř funkce:

import cython
 
@cython.cfunc
def add_two_numbers(x: cython.int, y: cython.int) -> cython.int:
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)
Poznámka: po překladu (resp. přesněji řečeno po transpřekladu) získáme naprosto stejný céčkovský kód, jako pro skript z předchozí kapitoly, takže si ho zde již nemusíme znovu uvádět.

11. Zákaz použití GILu v překládané funkci

Zbývá nám udělat ještě poslední krok, a to odstranit přípravu takzvaného kontextu na začátku těla funkce a naopak odstranění kontextu na jejím konci. Jedná se o tuto trojici řádků:

  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("add_two_numbers", 0);
  ...
  ...
  ...
  __Pyx_RefNannyFinishContext();

Zjednodušeně řečeno – kontext úzce souvisí s GIL (Global Interpreter Lock), který prozatím tvoří neoddělitelnou část klasického interpretru CPythonu (ovšem například v Jythonu ho nenajdeme, i když tento interpret má zase jiné problémy). GIL je zapotřebí nastavit tehdy, pokud naše (nyní vlastně céčková) funkce bude interně volat funkce naprogramované v Pythonu, popř. funkce z jeho interní knihovny. To my ovšem nepotřebujeme – pouze sčítáme dvě celá čísla a vracíme výsledek součtu – takže můžeme práci s GILem zcela zakázat, a to použitím specifikace nogil. Finální podoba naší funkce bude vypadat následovně:

cdef int add_two_numbers(int x, int y) nogil:
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

Ovšem zápis nogil za hlavičkou funkce pochopitelně není kompatibilní s Pythonem, který na tomto místě maximálně očekává typovou informaci. Musíme tedy specifikaci nogil umístit tam, kde bude syntakticky korektní. A tímto místem je opět dekorátor, který se nyní jmenuje @nogil, resp. @cython.nogil (podle způsobu jeho importu):

import cython
 
@cython.cfunc
@cython.nogil
def add_two_numbers(x: cython.int, y: cython.int) -> cython.int:
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

12. Volání funkcí ze standardní knihovny jazyka C

Odstranění GILu z nějaké funkce sice může vést ke zvýšení rychlosti, ale pojí se s tím jeden problém – naše funkce se sice bez GILu zdánlivě obejde, ovšem běžné Pythonovské funkce a operace z ní volané s ním počítají. Proto pokud použijeme nogil, budeme se muset v tomto bloku obejít bez podpory pythonních funkcí, takže tento kód již nebude korektní (nepůjde přeložit), protože se zde snažíme o volání pythonní funkce print:

cdef int add_two_numbers(int x, int y) nogil:
    print(x)
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

resp. v pythoní syntaxi:

import cython
 
@cython.cfunc
@cython.nogil
def add_two_numbers(x: cython.int, y: cython.int) -> cython.int:
    print(x)
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

V našem případě to nebude nic složitého – prostě použijeme funkci printf ze standardní céčkové knihovny (ta se stejně bude do výsledného binárního souboru linkovat z jiných důvodů). Řešení může vypadat následovně (pozor na slovo cimport a ne import):

from libc.stdio cimport printf
 
 
cdef int add_two_numbers(int x, int y) nogil:
    printf("%i\n", x)
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)

Při přepisu do syntaxe kompatibilní s Pythonem budeme muset nepatrně změnit i příkaz import, resp. přesněji řečeno jméno balíčku, který importujeme:

import cython
from cython.cimports.libc.stdio import printf
 
@cython.cfunc
@cython.nogil
def add_two_numbers(x: cython.int, y: cython.int) -> cython.int:
    printf("%d\n", x)
    return x + y
 
 
z = add_two_numbers(123, 456)
print(z)
Poznámka: tímto způsobem ovšem nebude možné upravit všechny funkce, takže některé z nich budou GIL (tedy zámek) neustále používat. Ovšem u funkcí s čistými výpočty apod. se většinou můžeme GILu velmi úspěšně zbavit (viz též další příklady).

13. Základní varianta benchmarku naprogramovaná v čistém Pythonu

Nyní již máme k dispozici všechny potřebné informace nutné pro úpravu našeho v několika článcích opakovaného benchmarku provádějícího výpočet Mandelbrotovy množiny. Naším cílem bude dosažení rychlosti srovnatelné s variantou naprogramovanou v čistém ANSI C:

#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 % 256];
            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;
}

Jen pro připomenutí – předchozí varianta, s níž jsme končili článek o RPythonu a Cythonu, vypadala následovně. Nejedná se o špatné řešení, ovšem již nyní je nutné říct, že ho můžeme vylepšit a cca dvojnásobně urychlit:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
# taken from Fractint
palette = (
        (255, 255, 255), (224, 224, 224), (216, 216, 216), (208, 208, 208),
        (200, 200, 200), (192, 192, 192), (184, 184, 184), (176, 176, 176),
        ...
        ...
        ...
        (240, 240, 140), (244, 244, 152), (244, 244, 168), (244, 244, 180),
        (244, 244, 196), (248, 248, 208), (248, 248, 224), (248, 248, 236),
        (252, 252, 252), (248, 248, 248), (240, 240, 240), (232, 232, 232))
 
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 __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)

Obrázek 6: Mandelbrotova množina o rozlišení 512×512 pixelů vykreslená předchozím příkladem.

14. Přepis benchmarku do Cythonu

Nyní si postupně zdrojový kód z předchozí kapitoly přepíšeme do podoby zpracovatelné Cythonem. Použijeme přitom původní syntaxi Pyrexu. Přepisovat budeme pouze funkci calc_mandelbrot, ve které se stráví prakticky 100% času.

Nejprve je vhodné změnit hlavičku funkce – určíme, že se má funkce přeložit do céčka (a posléze do nativního kódu) a nastavíme typy parametrů funkce:

cdef calc_mandelbrot(int width, int height, int maxiter, palette):

Explicitně určíme typy všech lokálních proměnných ve funkci:

    cdef double zx
    cdef double zy
    cdef double zx2
    cdef double zy2
    cdef double cx
    cdef double cy
    cdef int r
    cdef int g
    cdef int b
    cdef int i

To je prozatím vše, takže se podívejme na výsledný skript:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from sys import argv
 
# taken from Fractint
palette = (
        (255, 255, 255), (224, 224, 224), (216, 216, 216), (208, 208, 208),
        (200, 200, 200), (192, 192, 192), (184, 184, 184), (176, 176, 176),
        ...
        ...
        ...
        (240, 240, 140), (244, 244, 152), (244, 244, 168), (244, 244, 180),
        (244, 244, 196), (248, 248, 208), (248, 248, 224), (248, 248, 236),
        (252, 252, 252), (248, 248, 248), (240, 240, 240), (232, 232, 232))
 
cdef calc_mandelbrot(int width, int height, int maxiter, palette):
    cdef double zx
    cdef double zy
    cdef double zx2
    cdef double zy2
    cdef double cx
    cdef double cy
    cdef int r
    cdef int g
    cdef int b
    cdef int i
 
    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)

15. Varianta se syntaxí kompatibilní s Pythonem

Přepis ze syntaxe Cythonu/Pyrexu do formy plně kompatibilní se standardním Pythonem je již poměrně triviální a navíc veskrze mechanickou záležitostí. Hlavičku funkce uvodíme dekorátorem @cfunc a typy parametrů zapíšeme s využitím syntaxe totožné s typovými informacemi (type hints):

@cython.cfunc
def calc_mandelbrot(width: cython.int, height: cython.int, maxiter: cython.int, palette):

Podobně explicitně specifikujeme typy všech lokálních proměnných, které se zúčastní výpočtů:

    zx: cython.double
    zy: cython.double
    zx2: cython.double
    zy2: cython.double
    cx: cython.double
    cy: cython.double
    r: cython.int
    g: cython.int
    b: cython.int
    i: cython.int

A takto bude vypadat výsledný skript, který lze přeložit Cythonem do nativního a poměrně rychlého kódu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from sys import argv
import cython
 
# taken from Fractint
palette = (
        (255, 255, 255), (224, 224, 224), (216, 216, 216), (208, 208, 208),
        (200, 200, 200), (192, 192, 192), (184, 184, 184), (176, 176, 176),
        ...
        ...
        ...
        (244, 244, 196), (248, 248, 208), (248, 248, 224), (248, 248, 236),
        (252, 252, 252), (248, 248, 248), (240, 240, 240), (232, 232, 232))
 
@cython.cfunc
def calc_mandelbrot(width: cython.int, height: cython.int, maxiter: cython.int, palette):
    zx: cython.double
    zy: cython.double
    zx2: cython.double
    zy2: cython.double
    cx: cython.double
    cy: cython.double
    r: cython.int
    g: cython.int
    b: cython.int
    i: cython.int
 
    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)

16. Pole a předávání ukazatelů

Barvová paleta je prozatím realizována Pythonovskou n-ticí. Tento datový typ patří společně se seznamy k základním typům Pythonu, ovšem kvůli tomu, že jsou tyto datové struktury (kontejnery) nehomogenní, se s nimi musí zacházet poměrně neefektivně. Navíc je neefektivní i samotné uložení těchto datových typů v operační paměti. Řešením je použití klasických céčkových polí s případným převodem seznamů na pole. To lze provést například vytvořením klasického céčkového pole s prvky zadaného typu. Nejprve vytvoříme pole dle sémantiky samotného Pythonu:

from cpython cimport array
 
cdef array.array apalette = array.array('B')

Kdykoli později můžeme získat ukazatel na prvky pole:

apalette.data.as_uchars

Získáme tedy hodnotu typu unsigned char *, kterou předáme do funkce calc_mandelbrot, kde se s ní bude pracovat velmi efektivně. Navíc využijeme céčkovskou funkci printf a tudíž se můžeme zbavit GILu:

@cython.cdivision(True)
cdef void calc_mandelbrot(int width, int height, int maxiter, unsigned char *palette) nogil:

A takto bude vypadat výsledný skript pro výpočet, který je opět založen na syntaxi Cythonu/Pyrexu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from sys import argv, exit
import cython
from cpython cimport array
from libc.stdio cimport printf
 
# taken from Fractint
palette = [
        [255, 255, 255], [224, 224, 224], [216, 216, 216], [208, 208, 208],
        [200, 200, 200], [192, 192, 192], [184, 184, 184], [176, 176, 176],
        ...
        ...
        ...
        [244, 244, 196], [248, 248, 208], [248, 248, 224], [248, 248, 236],
        [252, 252, 252], [248, 248, 248], [240, 240, 240], [232, 232, 232]]
 
@cython.cdivision(True)
cdef void calc_mandelbrot(int width, int height, int maxiter, unsigned char *palette) nogil:
    cdef double zx
    cdef double zy
    cdef double zx2
    cdef double zy2
    cdef double cx
    cdef double cy
    cdef unsigned char r
    cdef unsigned char g
    cdef unsigned char b
    cdef int i
    cdef int index
 
    printf("P3\n%d %d\n255\n", width, height)
    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
 
            index = i * 3
            r = palette[index]
            g = palette[index+1]
            b = palette[index+2]
            printf("%d %d %d\n", r, g, b)
            cx += 3.0/width
        cy += 3.0/height
 
 
cdef array.array apalette = array.array('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])
 
    for color in palette:
        for component in color:
            apalette.append(component)
 
    calc_mandelbrot(width, height, maxiter, apalette.data.as_uchars)

17. Opětovná úprava se syntaxí kompatibilní s Pythonem

Pro úplnost si ukažme, jakým způsobem je možné skript z předchozí kapitoly přepsat do podoby plně kompatibilní s Cythonem.

Alokace pole s prvky typu bajt (bude prázdné):

from cython.cimports.cpython import array
import array
apalette = cython.declare(array.array, array.array('B', []))

Přidání prvků (barev) do barvové palety:

for color in palette:
    for component in color:
        apalette.append(component)

Získání ukazatele na první barvu:

apalette.data.as_uchars

Začátek funkce calc_mandelbrot bude vypadat následovně:

@cython.cdivision(True)
@cython.nogil
@cython.cfunc
def calc_mandelbrot(width: cython.int, height: cython.int, maxiter: cython.int, palette: cython.p_uchar) -> cython.int:
    zx: cython.double
    zy: cython.double
    zx2: cython.double
    zy2: cython.double
    cx: cython.double
    cy: cython.double
    r: cython.uchar
    g: cython.uchar
    b: cython.uchar
    i: cython.int
    index: cython.int

A takto vypadá celý skript:

zabbix_tip

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from sys import argv
import cython
from cython.cimports.cpython import array
import array
from cython.cimports.libc.stdio import printf
 
# taken from Fractint
palette = [
        [255, 255, 255], [224, 224, 224], [216, 216, 216], [208, 208, 208],
        [200, 200, 200], [192, 192, 192], [184, 184, 184], [176, 176, 176],
        ...
        ...
        ...
        [244, 244, 196], [248, 248, 208], [248, 248, 224], [248, 248, 236],
        [252, 252, 252], [248, 248, 248], [240, 240, 240], [232, 232, 232]]
 
 
@cython.cdivision(True)
@cython.nogil
@cython.cfunc
def calc_mandelbrot(width: cython.int, height: cython.int, maxiter: cython.int, palette: cython.p_uchar) -> cython.int:
    zx: cython.double
    zy: cython.double
    zx2: cython.double
    zy2: cython.double
    cx: cython.double
    cy: cython.double
    r: cython.uchar
    g: cython.uchar
    b: cython.uchar
    i: cython.int
    index: cython.int
 
    printf("P3\n%d %d\n255\n", width, height)
 
    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
 
            index = i * 3
            r = palette[index]
            g = palette[index+1]
            b = palette[index+2]
            printf("%d %d %d\n", r, g, b)
            cx += 3.0/width
        cy += 3.0/height
 
 
apalette = cython.declare(array.array, array.array('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])
 
    for color in palette:
        for component in color:
            apalette.append(component)
 
    calc_mandelbrot(width, height, maxiter, apalette.data.as_uchars)

18. Výsledek transpilace Cythonem do céčka

A na úplný závěr si ukažme, jak byla funkce pro výpočet Mandelbrotovy množiny transpilována do jazyka C:

static int __pyx_f_17mandelbrot_cython_calc_mandelbrot(int __pyx_v_width, int __pyx_v_height, int __pyx_v_maxiter, unsigned char *__pyx_v_palette) {
  double __pyx_v_zx;
  double __pyx_v_zy;
  double __pyx_v_zx2;
  double __pyx_v_zy2;
  double __pyx_v_cx;
  double __pyx_v_cy;
  unsigned char __pyx_v_r;
  unsigned char __pyx_v_g;
  unsigned char __pyx_v_b;
  int __pyx_v_i;
  int __pyx_v_index;
  CYTHON_UNUSED long __pyx_v_y;
  CYTHON_UNUSED long __pyx_v_x;
  int __pyx_r;
  int __pyx_t_1;
  int __pyx_t_2;
  long __pyx_t_3;
  int __pyx_t_4;
  int __pyx_t_5;
  long __pyx_t_6;
  int __pyx_t_7;
 
  (void)(printf(((char const *)"P3\n%d %d\n255\n"), __pyx_v_width, __pyx_v_height));
 
  __pyx_v_cy = -1.5;
  __pyx_t_1 = __pyx_v_height;
  __pyx_t_2 = __pyx_t_1;
  for (__pyx_t_3 = 0; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) {
    __pyx_v_y = __pyx_t_3;
    __pyx_v_cx = -2.0;
    __pyx_t_4 = __pyx_v_width;
    __pyx_t_5 = __pyx_t_4;
    for (__pyx_t_6 = 0; __pyx_t_6 < __pyx_t_5; __pyx_t_6+=1) {
      __pyx_v_x = __pyx_t_6;
      __pyx_v_zx = 0.0;
      __pyx_v_zy = 0.0;
      __pyx_v_i = 0;
      while (1) {
        __pyx_t_7 = (__pyx_v_i < __pyx_v_maxiter);
        if (!__pyx_t_7) break;
        __pyx_v_zx2 = (__pyx_v_zx * __pyx_v_zx);
        __pyx_v_zy2 = (__pyx_v_zy * __pyx_v_zy);
        __pyx_t_7 = ((__pyx_v_zx2 + __pyx_v_zy2) > 4.0);
        if (__pyx_t_7) {
          goto __pyx_L8_break;
        }
        __pyx_v_zy = (((2.0 * __pyx_v_zx) * __pyx_v_zy) + __pyx_v_cy);
        __pyx_v_zx = ((__pyx_v_zx2 - __pyx_v_zy2) + __pyx_v_cx);
        __pyx_v_i = (__pyx_v_i + 1);
      }
      __pyx_L8_break:;
      __pyx_v_index = (__pyx_v_i * 3);
      __pyx_v_r = (__pyx_v_palette[__pyx_v_index]);
      __pyx_v_g = (__pyx_v_palette[(__pyx_v_index + 1)]);
      __pyx_v_b = (__pyx_v_palette[(__pyx_v_index + 2)]);
      (void)(printf(((char const *)"%d %d %d\n"), __pyx_v_r, __pyx_v_g, __pyx_v_b));
      __pyx_v_cx = (__pyx_v_cx + (3.0 / ((double)__pyx_v_width)));
    }
    __pyx_v_cy = (__pyx_v_cy + (3.0 / ((double)__pyx_v_height)));
  }
  __pyx_r = 0;
  return __pyx_r;
}
Poznámka: až na matoucí jména proměnných se jedná o velmi přímočarý kód, který bude překladačem céčka dobře optimalizovatelný.

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

Všechny demonstrační příklady (resp. jednoduché benchmarky) ukazující vlastnosti Cythonu naleznete v repositáři https://github.com/tisnik/most-popular-python-libs:

# Příklad Stručný popis Adresa
1 add_numbers/add_numbers1.py běžná funkce v Pythonu podporující duck typing https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers1.py
2 add_numbers/add_numbers2.pyx funkce určená pro překlad do céčka, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers2.pyx
3 add_numbers/add_numbers2.py funkce určená pro překlad do céčka, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers2.py
4 add_numbers/add_numbers3.pyx přidání informace o typech parametrů funkce pro součet celých čísel, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers3.pyx
5 add_numbers/add_numbers3.py přidání informace o typech parametrů funkce pro součet celých čísel, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers3.py
6 add_numbers/add_numbers4.pyx přidání informace o návratovém typu funkce pro součet celých čísel, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers4.pyx
7 add_numbers/add_numbers4.py přidání informace o návratovém typu funkce pro součet celých čísel, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers4.py
8 add_numbers/add_numbers5.pyx zákaz použití GILu v překládané funkci, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers5.pyx
9 add_numbers/add_numbers5.py zákaz použití GILu v překládané funkci, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers5.py
10 add_numbers/add_numbers6.pyx zákaz použití GILu v překládané funkci, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers6.pyx
11 add_numbers/add_numbers6.py zákaz použití GILu v překládané funkci, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers6.py
12 add_numbers/add_numbers7.pyx volání funkcí ze standardní knihovny jazyka C, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers7.pyx
13 add_numbers/add_numbers7.py volání funkcí ze standardní knihovny jazyka C, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers7.py
 
14 add_numbers/add_numbers_dis.py zobrazení bajtkódu Pythonu po překladu funkce add_two_numbers https://github.com/tisnik/most-popular-python-libs/blob/master/cython/ad­d_numbers/add_numbers_dis­.py
 
15 mandelbrot/v1_python benchmark s výpočtem Mandelbrotovy množiny, původní varianta naprogramovaná v čistém Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/man­delbrot/v1_python
16 mandelbrot/v2_cython přidání typových informací do funkce calc_mandelbrot, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/man­delbrot/v2_cython
17 mandelbrot/v2_python přidání typových informací do funkce calc_mandelbrot, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/man­delbrot/v2_python
18 mandelbrot/v3_cython alokace a použití céčkového pole, původní syntaxe Cythonu/Pyrexu https://github.com/tisnik/most-popular-python-libs/blob/master/cython/man­delbrot/v3_cython
19 mandelbrot/v3-python alokace a použití céčkového pole, syntaxe kompatibilní s Pythonem https://github.com/tisnik/most-popular-python-libs/blob/master/cython/mandelbrot/v3-python

20. Odkazy na Internetu

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