Cos'è Cython? Python alla velocità di C

Python ha la reputazione di essere uno dei linguaggi di programmazione più convenienti, riccamente attrezzati e decisamente utili. Velocità di esecuzione? Non così tanto.

Entra Cython. Il linguaggio Cython è un superset di Python che si compila in C, producendo miglioramenti delle prestazioni che possono variare da pochi punti percentuali a diversi ordini di grandezza, a seconda dell'attività da svolgere. Per il lavoro che è vincolato dai tipi di oggetti nativi di Python, le accelerazioni non saranno grandi. Ma per le operazioni numeriche o per qualsiasi operazione che non coinvolga gli interni di Python, i guadagni possono essere enormi. 

Con Cython, puoi aggirare molte delle limitazioni native di Python o trascenderle completamente, senza dover rinunciare alla facilità e alla praticità di Python. In questo articolo, esamineremo i concetti di base alla base di Cython e creeremo una semplice applicazione Python che utilizza Cython per accelerare una delle sue funzioni.

Video correlato: utilizzo di Cython per velocizzare Python

Compilare Python in C

Il codice Python può effettuare chiamate direttamente nei moduli C. Questi moduli C possono essere librerie C generiche o librerie create appositamente per funzionare con Python. Cython genera il secondo tipo di modulo: le librerie C che parlano con gli interni di Python e che possono essere raggruppate con il codice Python esistente.

Il codice Cython assomiglia molto al codice Python, in base alla progettazione. Se fornisci al compilatore Cython un programma Python (Python 2.x e Python 3.x sono entrambi supportati), Cython lo accetterà così com'è, ma nessuna delle accelerazioni native di Cython entrerà in gioco. Ma se decori il codice Python con annotazioni di tipo nella sintassi speciale di Cython, Cython sarà in grado di sostituire gli equivalenti C veloci con oggetti Python lenti.

Nota che l'approccio di Cython è  incrementale . Ciò significa che uno sviluppatore può iniziare con  un'applicazione Python esistente e velocizzarla apportando modifiche puntuali al codice, anziché riscrivere l'intera applicazione da zero.

Questo approccio combacia con la natura dei problemi di prestazioni del software in generale. Nella maggior parte dei programmi, la stragrande maggioranza del codice ad alta intensità di CPU è concentrata in pochi punti caldi, una versione del principio di Pareto, noto anche come regola "80/20". Quindi la maggior parte del codice in un'applicazione Python non ha bisogno di essere ottimizzata per le prestazioni, solo alcuni pezzi critici. Puoi tradurre in modo incrementale quei punti caldi in Cython e ottenere così i miglioramenti delle prestazioni di cui hai bisogno dove è più importante. Il resto del programma può rimanere in Python per comodità degli sviluppatori.

Come usare Cython

Considera il seguente codice, tratto dalla documentazione di Cython:

def f (x):

    return x ** 2-x

def integrate_f (a, b, N):

    s = 0

    dx = (ba) / N

    per i nell'intervallo (N):

        s + = f (a + i * dx)

    return s * dx

Questo è un esempio di giocattolo, un'implementazione non molto efficiente di una funzione integrale. In quanto codice Python puro, è lento, perché Python deve convertire avanti e indietro tra i tipi numerici nativi della macchina ei propri tipi di oggetti interni.

Consideriamo ora la versione Cython dello stesso codice, con le aggiunte di Cython sottolineate:

 cdef doppia f (doppia x):

    return x ** 2-x

def integrate_f (doppia a, doppia b, int N):

    cdef int i

    cdef doppia s, x, dx

    s = 0

    dx = (ba) / N

    per i nell'intervallo (N):

        s + = f (a + i * dx)

    return s * dx

Se si dichiara esplicitamente i tipi di variabili, sia per i parametri delle funzioni e le variabili utilizzate nel corpo della funzione ( double, intecc), Cython tradurrà tutto questo in C. Si può utilizzare anche la cdefparola chiave per definire funzioni che sono implementato principalmente in C per una maggiore velocità, sebbene queste funzioni possano essere chiamate solo da altre funzioni Cython e non da script Python. (Nell'esempio sopra, integrate_fpuò essere chiamato solo da un altro script Python.)

Nota quanto poco è cambiato il nostro codice effettivo  . Tutto ciò che abbiamo fatto è aggiungere dichiarazioni di tipo al codice esistente per ottenere un significativo aumento delle prestazioni.

Vantaggi di Cython

Oltre ad essere in grado di velocizzare il codice che hai già scritto, Cython garantisce molti altri vantaggi:

Lavorare con le librerie C esterne può essere più veloce

Pacchetti Python come NumPy avvolgono le librerie C nelle interfacce Python per renderle facili da lavorare. Tuttavia, andare avanti e indietro tra Python e C attraverso questi wrapper può rallentare le cose. Cython ti consente di parlare direttamente con le librerie sottostanti, senza Python in mezzo. (Sono supportate anche le librerie C ++.)

È possibile utilizzare sia la gestione della memoria C che Python

Se usi oggetti Python, sono gestiti dalla memoria e raccolti in modo indesiderato come nel normale Python. Ma se vuoi creare e gestire le tue strutture di livello C e usare malloc/ freeper lavorare con esse, puoi farlo. Ricorda solo di ripulire te stesso.

Puoi optare per la sicurezza o la velocità secondo necessità 

Cython esegue automaticamente controlli di runtime per problemi comuni che compaiono in C, come l'accesso fuori limite su un array, tramite decoratori e direttive del compilatore (ad esempio @boundscheck(False)). Di conseguenza, il codice C generato da Cython è molto più sicuro per impostazione predefinita del codice C eseguito manualmente, sebbene potenzialmente a scapito delle prestazioni grezze.

Se sei sicuro di non aver bisogno di questi controlli in fase di esecuzione, puoi disabilitarli per ulteriori aumenti di velocità, su un intero modulo o solo su determinate funzioni.

Cython consente inoltre di accedere in modo nativo alle strutture Python che utilizzano il protocollo buffer per l'accesso diretto ai dati archiviati in memoria (senza copiatura intermedia). Le visioni della memoria di Cython ti consentono di lavorare con quelle strutture ad alta velocità e con il livello di sicurezza appropriato per il compito. Ad esempio, i dati grezzi alla base di una stringa Python possono essere letti in questo modo (veloce) senza dover passare attraverso il runtime Python (lento).

Il codice Cython C può trarre vantaggio dal rilascio del GIL

Il Global Interpreter Lock di Python, o GIL, sincronizza i thread all'interno dell'interprete, proteggendo l'accesso agli oggetti Python e gestendo la contesa per le risorse. Ma il GIL è stato ampiamente criticato come un ostacolo per un Python con prestazioni migliori, specialmente su sistemi multicore.

Se hai una sezione di codice che non fa riferimento a oggetti Python ed esegue un'operazione di lunga durata, puoi contrassegnarla con la  with nogil:direttiva per consentirne l'esecuzione senza GIL. Ciò libera l'interprete Python di fare altre cose e consente al codice Cython di utilizzare più core (con lavoro aggiuntivo).

Cython può utilizzare la sintassi dei suggerimenti di tipo Python 

Python ha una sintassi di suggerimento del tipo che viene utilizzata principalmente da linter e controllori di codice, piuttosto che dall'interprete CPython. Cython ha una propria sintassi personalizzata per le decorazioni del codice, ma con le recenti revisioni di Cython è possibile utilizzare la sintassi di suggerimento del tipo Python per fornire suggerimenti di tipo di base anche a Cython. 

Cython può essere utilizzato per oscurare il codice Python sensibile

I moduli Python sono banalmente facili da decompilare e ispezionare, ma i binari compilati non lo sono. Quando si distribuisce un'applicazione Python agli utenti finali, se si desidera proteggere alcuni dei suoi moduli dallo snooping casuale, è possibile farlo compilandoli con Cython. Nota, tuttavia, questo è un effetto collaterale delle capacità di Cython, non una delle sue funzioni previste.

Limitazioni di Cython

Tieni presente che Cython non è una bacchetta magica. Non trasforma automaticamente ogni istanza del codice Python in codice C sfrigolante. Per ottenere il massimo da Cython, devi usarlo con saggezza e comprenderne i limiti:

Poca velocità per il codice Python convenzionale

Quando Cython incontra il codice Python non può tradurlo completamente in C, trasforma quel codice in una serie di chiamate C agli interni di Python. Ciò equivale a togliere l'interprete di Python dal ciclo di esecuzione, il che fornisce al codice un modesto aumento del 15-20% di velocità per impostazione predefinita. Notare che questo è lo scenario migliore; in alcune situazioni, potresti non notare alcun miglioramento delle prestazioni o addirittura un degrado delle prestazioni.

Poca velocità per le strutture dati native di Python

Python fornisce una serie di strutture dati: stringhe, elenchi, tuple, dizionari e così via. Sono estremamente convenienti per gli sviluppatori e vengono forniti con la loro gestione automatica della memoria. Ma sono più lenti del puro C.

Cython ti consente di continuare a utilizzare tutte le strutture dati di Python, anche se senza troppa velocità. Questo è, ancora una volta, perché Cython chiama semplicemente le API C nel runtime Python che creano e manipolano quegli oggetti. Pertanto, le strutture dati Python si comportano in modo molto simile al codice Python ottimizzato da Cython in generale: a volte ottieni una spinta, ma solo leggermente. Per ottenere i migliori risultati, usa le variabili e le strutture C. La buona notizia è che Cython rende facile lavorare con loro.

Il codice Cython è più veloce quando "puro C"

Se hai una funzione in C etichettata con la cdefparola chiave, con tutte le sue variabili e chiamate di funzioni inline ad altre cose che sono puro C, verrà eseguito il più velocemente possibile. Ma se quella funzione fa riferimento a qualsiasi codice nativo di Python, come una struttura dati Python o una chiamata a un'API Python interna, quella chiamata sarà un collo di bottiglia delle prestazioni.

Fortunatamente, Cython fornisce un modo per individuare questi colli di bottiglia: un rapporto sul codice sorgente che mostra a colpo d'occhio quali parti della tua app Cython sono in puro C e quali interagiscono con Python. Migliore è l'ottimizzazione dell'app, minore sarà l'interazione con Python.

Cython NumPy 

Cython migliora l'uso di librerie di elaborazione dei numeri di terze parti basate su C come NumPy. Poiché il codice Cython si compila in C, può interagire direttamente con quelle librerie e rimuovere i colli di bottiglia di Python dal ciclo.

Ma NumPy, in particolare, funziona bene con Cython. Cython ha il supporto nativo per costruzioni specifiche in NumPy e fornisce un rapido accesso agli array NumPy. E la stessa sintassi NumPy familiare che useresti in uno script Python convenzionale può essere utilizzata in Cython così com'è.

Tuttavia, se vuoi creare le associazioni più vicine possibili tra Cython e NumPy, devi decorare ulteriormente il codice con la sintassi personalizzata di Cython. L'  cimportistruzione, ad esempio, consente al codice Cython di vedere i costrutti di livello C nelle librerie in fase di compilazione per i collegamenti più veloci possibili.

Poiché NumPy è così ampiamente utilizzato, Cython supporta NumPy "fuori dagli schemi". Se hai installato NumPy, puoi semplicemente dichiarare il  cimport numpy tuo codice, quindi aggiungere ulteriori decorazioni per utilizzare le funzioni esposte. 

Cython profiling e prestazioni

Ottieni le migliori prestazioni da qualsiasi parte di codice profilandola e vedendo in prima persona dove si trovano i colli di bottiglia. Cython fornisce hook per il modulo cProfile di Python, quindi puoi usare gli strumenti di profiling di Python, come cProfile, per vedere come si comporta il tuo codice Cython. 

Aiuta a ricordare in tutti i casi che Cython non è magico - che le pratiche di performance del mondo reale sono ancora valide. Meno navighi avanti e indietro tra Python e Cython, più velocemente verrà eseguita la tua app.

Ad esempio, se hai una raccolta di oggetti che desideri elaborare in Cython, non iterare su di essa in Python e invoca una funzione Cython ad ogni passaggio. Passa l'intera raccolta al tuo modulo Cython e ripeti lì. Questa tecnica viene utilizzata spesso nelle librerie che gestiscono i dati, quindi è un buon modello da emulare nel proprio codice.

Usiamo Python perché fornisce comodità al programmatore e consente uno sviluppo veloce. A volte la produttività del programmatore viene a scapito delle prestazioni. Con Cython, solo un piccolo sforzo in più può darti il ​​meglio di entrambi i mondi.

Ulteriori informazioni su Python

  • Cos'è Python? Programmazione potente e intuitiva
  • Cos'è PyPy? Python più veloce senza dolore
  • Cos'è Cython? Python alla velocità di C
  • Tutorial Cython: come velocizzare Python
  • Come installare Python in modo intelligente
  • Le migliori nuove funzionalità di Python 3.8
  • Migliore gestione dei progetti Python con Poetry
  • Virtualenv e venv: spiegazione degli ambienti virtuali Python
  • Python virtualenv e venv cosa fare e cosa non fare
  • Spiegazione del threading e dei sottoprocessi di Python
  • Come utilizzare il debugger Python
  • Come usare timeit per profilare il codice Python
  • Come usare cProfile per profilare il codice Python
  • Inizia con async in Python
  • Come usare asyncio in Python
  • Come convertire Python in JavaScript (e viceversa)
  • Python 2 EOL: come sopravvivere alla fine di Python 2
  • 12 Pythons per ogni esigenza di programmazione
  • 24 librerie Python per ogni sviluppatore Python
  • 7 dolci IDE Python che potresti aver perso
  • 3 principali carenze di Python e le loro soluzioni
  • 13 framework web Python a confronto
  • 4 framework di test Python per schiacciare i tuoi bug
  • 6 fantastiche nuove funzionalità di Python da non perdere
  • 5 distribuzioni Python per padroneggiare l'apprendimento automatico
  • 8 fantastiche librerie Python per l'elaborazione del linguaggio naturale