Accelerare Python con Cython

Cython è un superset di Python che ti consente di migliorare significativamente la velocità del tuo codice. È possibile aggiungere dichiarazioni di tipo opzionali per vantaggi ancora maggiori. Cython traduce il tuo codice in C / C ++ ottimizzato che viene compilato in un modulo di estensione Python. 

In questo tutorial imparerai come installare Cython, ottenere un incremento immediato delle prestazioni del tuo codice Python gratis, e poi come sfruttare realmente Cython aggiungendo tipi e profilando il tuo codice. Infine, apprenderai argomenti più avanzati come l'integrazione con codice C / C ++ e NumPy che puoi esplorare ulteriormente per ottenere guadagni ancora maggiori.

Conteggio dei tripli pitagorici

Pitagora era un matematico e filosofo greco. È famoso per il suo teorema di Pitagora, che afferma che in un triangolo rettangolo, la somma dei quadrati delle gambe dei triangoli è uguale al quadrato dell'ipotenusa. I tripli pitagorici sono tutti e tre i numeri interi positivi a, b e c tali che a² + b² = c². Ecco un programma che trova tutti i tripli pitagorici i cui membri non superano il limite previsto.

tempo limite di importazione (limite): risultato = 0 per un intervallo compreso (1, limite + 1): per b nell'intervallo (a + 1, limite + 1): per c nell'intervallo (b + 1, limite + 1) : se c * c> a * a + b * b: break if c * c == (a * a + b * b): risultato + = 1 risultato risultato se __name__ == '__main__': start = time.time () result = count (1000) duration = time.time () - avvia stampa (risultato, durata) Output: 881 13.883624076843262 

Apparentemente ci sono 881 triple, e il programma ha impiegato meno di 14 secondi per scoprirlo. Non è troppo lungo, ma abbastanza lungo per essere noioso. Se vogliamo trovare più tripli fino a un limite superiore, dovremmo trovare un modo per farlo andare più veloce. 

Si scopre che ci sono algoritmi sostanzialmente migliori, ma oggi ci stiamo concentrando sul rendere Python più veloce con Cython, non sul miglior algoritmo per trovare le triple di Pitagora. 

Amplificazione facile con pyximport

Il modo più semplice per usare Cython è usare la speciale funzione pyximport. Questa è una dichiarazione che compila il tuo codice Cython al volo e ti consente di godere dei vantaggi dell'ottimizzazione nativa senza troppi problemi. 

È necessario inserire il codice in cythonize nel proprio modulo, scrivere una riga di configurazione nel programma principale e quindi importarlo come al solito. Vediamo come appare. Ho spostato la funzione sul proprio file chiamato pythagorean_triples.pyx. L'estensione è importante per Cython. La linea che attiva Cython è import pyximport; pyximport.install (). Quindi importa solo il modulo con il contare() funzione e successivamente la invoca nella funzione principale.

importazione tempo importazione pyximport; pyximport.install () import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - avvia stampa (risultato, durata) se __name__ == '__main__': output principale (): 881 9.432806253433228 

La pura funzione Python ha funzionato il 50% in più. Abbiamo ottenuto questa spinta aggiungendo una singola riga. Non è affatto male.

Crea il tuo modulo di estensione

Mentre pyximport è veramente conveniente durante lo sviluppo, funziona solo su moduli Python puri. Spesso durante l'ottimizzazione del codice si desidera fare riferimento alle librerie C native o ai moduli di estensione Python. 

Per supportare quelli, e anche per evitare la compilazione dinamica in ogni esecuzione, è possibile creare il proprio modulo di estensione Cython. È necessario aggiungere un piccolo file setup.py e ricordarsi di crearlo prima di eseguire il programma ogni volta che si modifica il codice Cython. Ecco il file setup.py:

da distutils.core setup di importazione da Cython.Build import cythonize setup (ext_modules = cythonize ("pythagorean_triples.pyx"))

Quindi devi costruirlo:

$ python setup.py build_ext --inplace Compilazione di pythagorean_triples.pyx perché è cambiato. [1/1] Cythonizing pythagorean_triples.pyx con build_ext costruzione 'estensione pythagorean_triples' creando build creando build / temp.macosx-10.7-x86_64-3.6 gcc -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g - fwrapv -O3 -Wall -Wintict-prototypes -I / Users / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Users / gigi.sayfan / miniconda3 / envs / py3 / include -arch x86_64 -I / Utenti / gigi.sayfan / miniconda3 / envs / py3 / include / python3.6m -c pythagorean_triples.c -o build / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o gcc -bundle -undefined dynamic_lookup -L / Users / gigi.sayfan / miniconda3 / envs / py3 / lib -L / Users / gigi.sayfan / miniconda3 / envs / py3 / lib -arch x86_64 build / temp.macosx-10.7-x86_64-3.6 / pythagorean_triples.o -L / Users / gigi.sayfan / miniconda3 / envs / py3 / lib -o pythagorean_triples.cpython-36m-darwin.so

Come puoi vedere dall'output, Cython ha generato un file C chiamato pythagorean_triples.c e lo compila in un file .so specifico per piattaforma, che è il modulo di estensione che Python può ora importare come qualsiasi altro modulo di estensione nativo. 

Se sei curioso, dai un'occhiata al codice C generato. È molto lungo (2789 linee), ottuso, e contiene molte cose extra necessarie per lavorare con l'API Python. Lasciamo il pyximport ed eseguiamo nuovamente il nostro programma:

import time import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - avvia stampa (risultato, durata) if __name__ == '__main__': main () 881 9.507064819335938 

Il risultato è praticamente lo stesso di pyximport. Tuttavia, si noti che sto misurando solo il runtime del codice cythonized. Non sto misurando quanto tempo ci vuole pyximport per compilare il codice cythonized al volo. Nei grandi programmi, questo può essere significativo.

Aggiunta di tipi al tuo codice

Portiamolo al livello successivo. Cython è più di Python e aggiunge la digitazione opzionale. Qui, definisco semplicemente tutte le variabili come numeri interi e le prestazioni salgono alle stelle:

# pythagorean_triples.pyx def count (limite): cdef int risultato = 0 cdef int a = 0 cdef int b = 0 cdef int c = 0 per a in intervallo (1, limite + 1): per b nell'intervallo (a + 1 , limite + 1): per c nell'intervallo (b + 1, limite + 1): se c * c> a * a + b * b: interrompi se c * c == (a * a + b * b): risultato + = 1 risultato di ritorno ---------- # main.py tempo di importazione importazione pyximport; pyximport.install () import pythagorean_triples def main (): start = time.time () result = pythagorean_triples.count (1000) duration = time.time () - avvia stampa (risultato, durata) se __name__ == '__main__': main () Output: 881 0.056414127349853516 

Sì. È corretto. Definendo un paio di interi, il programma viene eseguito in meno di 57 millisecondi, rispetto a più di 13 secondi con Python puro. Questo è quasi un miglioramento 250X.

Profiling Your Code

Ho usato il modulo temporale di Python, che misura il tempo di parete ed è piuttosto buono il più delle volte. Se si desidera una temporizzazione più precisa dei frammenti di codice ridotto, prendere in considerazione l'utilizzo del modulo timeit. Ecco come misurare le prestazioni del codice utilizzando timeit:

>>> tempo di importazione >>> timeit.timeit ('count (1000)', setup = "dal conteggio delle importazioni di pythagorean_triples", numero = 1) 0.05357028398429975 # In esecuzione 10 volte >>> timeit.timeit ('count (1000)' , setup = "dal conteggio delle importazioni di pythagorean_triples", numero = 10) 0.5446877249924 

Il timeit () la funzione accetta una dichiarazione da eseguire, un codice di configurazione non misurato e il numero di volte in cui eseguire il codice misurato.

Argomenti avanzati

Ho appena graffiato la superficie qui. Puoi fare molto di più con Cython. Ecco alcuni argomenti che possono migliorare ulteriormente le prestazioni del tuo codice o consentire a Cython di integrarsi con altri ambienti:

  • chiamare il codice C
  • interagire con l'API di Python C e GIL
  • usando C ++ in Python
  • Porting di codice Cython in PyPY
  • usando il parallelismo
  • Cython e NumPy
  • condivisione di dichiarazioni tra i moduli Cython

Conclusione

Cython può produrre due ordini di grandezza di miglioramento delle prestazioni con uno sforzo minimo. Se sviluppi software non banale in Python, Cython è un gioco da ragazzi. Ha pochissimo overhead e puoi introdurlo gradualmente al tuo codice base.

Inoltre, non esitare a vedere ciò che abbiamo a disposizione per la vendita e per studiare nel mercato, e non esitare a fare domande e fornire il tuo prezioso feedback utilizzando il feed qui sotto.