Python si è guadagnato la reputazione di essere potente, flessibile e facile da usare. Queste virtù hanno portato al suo utilizzo in un’enorme e crescente varietà di applicazioni, flussi di lavoro e campi. Ma il design del linguaggio – la sua natura interpretata, il suo dinamismo di esecuzione – significa che Python è sempre stato un ordine di grandezza più lento dei linguaggi nativi come il C o il C++.
Nel corso degli anni, gli sviluppatori hanno escogitato una varietà di soluzioni per i limiti di velocità di Python. Per esempio, si potrebbero scrivere compiti ad alte prestazioni in C e avvolgerli con Python; molte librerie di apprendimento automatico fanno esattamente questo. Oppure si potrebbe usare Cython, un progetto che permette di cospargere il codice Python con informazioni di tipo runtime che permettono di compilarlo in C.
Ma i workaround non sono mai ideali. Non sarebbe fantastico se potessimo semplicemente prendere un programma Python esistente così com’è, ed eseguirlo drammaticamente più veloce? Questo è esattamente ciò che PyPy ti permette di fare.
PyPy vs. CPython
PyPy è un sostituto drop-in dell’interprete Python di serie, CPython. Mentre CPython compila Python in bytecode intermedio che viene poi interpretato da una macchina virtuale, PyPy usa la compilazione just-in-time (JIT) per tradurre il codice Python in linguaggio assembly nativo della macchina.
A seconda del compito che viene eseguito, i guadagni di prestazioni possono essere drammatici. In media, PyPy accelera Python di circa 7,6 volte, con alcuni compiti accelerati di 50 volte o più. L’interprete CPython semplicemente non esegue lo stesso tipo di ottimizzazioni di PyPy, e probabilmente non lo farà mai, dato che questo non è uno dei suoi obiettivi di progettazione.
La parte migliore è che è richiesto poco o nessun sforzo da parte dello sviluppatore per sbloccare i guadagni che PyPy fornisce. Basta scambiare CPython con PyPy, e per la maggior parte del tempo siete a posto. Ci sono alcune eccezioni, discusse più avanti, ma l’obiettivo dichiarato di PyPy è quello di eseguire codice Python esistente e non modificato e fornirgli un aumento automatico di velocità.
PyPy attualmente supporta sia Python 2 che Python 3, attraverso diverse incarnazioni del progetto. In altre parole, è necessario scaricare diverse versioni di PyPy a seconda della versione di Python che verrà eseguita. Il ramo Python 2 di PyPy è stato in giro molto più a lungo, ma la versione Python 3 è stata aggiornata di recente. Attualmente supporta sia Python 3.5 (qualità di produzione) che Python 3.6 (qualità beta).
Oltre a supportare tutto il nucleo del linguaggio Python, PyPy funziona con la maggior parte degli strumenti dell’ecosistema Python, come pip
per il packaging o virtualenv
per gli ambienti virtuali. La maggior parte dei pacchetti Python, anche quelli con moduli C, dovrebbe funzionare così com’è, sebbene ci siano limitazioni che approfondiremo più avanti.
Come funziona PyPy
PyPy usa tecniche di ottimizzazione trovate in altri compilatori just-in-time per linguaggi dinamici. Analizza i programmi Python in esecuzione per determinare le informazioni di tipo degli oggetti mentre vengono creati e usati nei programmi, poi usa queste informazioni di tipo come guida per velocizzare le cose. Per esempio, se una funzione Python lavora solo con uno o due diversi tipi di oggetti, PyPy genera codice macchina per gestire quei casi specifici.
Le ottimizzazioni di PyPy sono gestite automaticamente in fase di esecuzione, quindi generalmente non è necessario modificare le sue prestazioni. Un utente avanzato potrebbe sperimentare con le opzioni a riga di comando di PyPy per generare codice più veloce per casi speciali, ma solo raramente è necessario.
PyPy si discosta anche dal modo in cui CPython gestisce alcune funzioni interne, ma cerca di mantenere comportamenti compatibili. Per esempio, PyPy gestisce la garbage collection in modo diverso da CPython. Non tutti gli oggetti sono immediatamente raccolti una volta usciti dallo scope, così un programma Python che gira sotto PyPy può mostrare un’impronta di memoria più grande di quando gira sotto CPython. Ma potete ancora usare i controlli della garbage collection di alto livello di Python esposti attraverso il modulo gc
, come gc.enable()
, gc.disable()
, e gc.collect()
.
Se volete informazioni sul comportamento JIT di PyPy in fase di esecuzione, PyPy include un modulo, pypyjit
, che espone molti hooks JIT alla vostra applicazione Python. Se avete una funzione o un modulo che sembra funzionare male con il JIT, pypyjit
vi permette di ottenere statistiche dettagliate su di esso.
Un altro modulo specifico per PyPy, __pypy__
, espone altre caratteristiche specifiche di PyPy, quindi può essere utile per scrivere applicazioni che sfruttano queste caratteristiche. A causa del dinamismo del runtime di Python, è possibile costruire applicazioni Python che usano queste caratteristiche quando PyPy è presente e le ignorano quando non lo è.
Limitazioni di PyPyPy
Per quanto PyPy possa sembrare magico, non lo è. PyPy ha alcune limitazioni che riducono o annullano la sua efficacia per certi tipi di programmi. Ahimè, PyPy non è un rimpiazzo completamente universale per il runtime CPython di serie.
PyPy funziona meglio con applicazioni Python pure
PyPy ha sempre funzionato meglio con applicazioni Python “pure” – cioè, applicazioni scritte in Python e nient’altro. I pacchetti Python che si interfacciano con le librerie C, come NumPy, non sono andati altrettanto bene a causa del modo in cui PyPy emula le interfacce binarie native di CPython.
Gli sviluppatori di PyPy hanno eliminato questo problema e hanno reso PyPy più compatibile con la maggior parte dei pacchetti Python che dipendono dalle estensioni C. Numpy, per esempio, funziona molto bene con PyPy ora. Ma se volete la massima compatibilità con le estensioni C, usate CPython.
PyPy funziona meglio con programmi a lunga durata
Uno degli effetti collaterali di come PyPy ottimizza i programmi Python è che i programmi a lunga durata beneficiano maggiormente delle sue ottimizzazioni. Più a lungo il programma viene eseguito, più informazioni di tipo run-time PyPy può raccogliere, e più ottimizzazioni può fare. Gli script Python one-and-done non beneficeranno di questo tipo di cose. Le applicazioni che ne beneficiano hanno tipicamente dei cicli che vengono eseguiti per lunghi periodi di tempo, o vengono eseguiti continuamente in background – i framework web, per esempio.
PyPy non fa una compilazione anticipata
PyPy compila il codice Python, ma non è un compilatore di codice Python. A causa del modo in cui PyPy esegue le sue ottimizzazioni e il dinamismo intrinseco di Python, non c’è modo di emettere il codice JIT risultante come un binario autonomo e riutilizzarlo. Ogni programma deve essere compilato per ogni esecuzione. Se volete compilare Python in un codice più veloce che possa essere eseguito come applicazione standalone, usate Cython, Numba, o il progetto Nuitka, attualmente sperimentale.