Multicore Python: un obiettivo difficile, degno e raggiungibile

Per tutte le fantastiche e convenienti funzionalità di Python, un obiettivo rimane fuori portata: le app Python in esecuzione sull'interprete di riferimento CPython e che utilizzano più core della CPU in parallelo.

Questo è stato a lungo uno dei più grandi ostacoli di Python, soprattutto perché tutte le soluzioni alternative sono goffe. L'urgenza di trovare una soluzione a lungo termine al problema sta crescendo, in particolare poiché il numero di core sui processori continua a crescere (vedi il colosso a 24 core di Intel).

Una serratura per tutti

In verità, è possibile utilizzare i thread nelle applicazioni Python, molti di loro lo fanno già. Ciò che  non è possibile per CPython è di eseguire applicazioni multithread con ogni thread eseguito in parallelo su un core diverso. La gestione della memoria interna di CPython non è thread-safe, quindi l'interprete esegue solo un thread alla volta, passando da uno all'altro secondo necessità e controllando l'accesso allo stato globale.

Questo meccanismo di blocco, il Global Interpreter Lock (GIL), è il motivo principale per cui CPython non può eseguire thread in parallelo. Ci sono alcuni fattori attenuanti; ad esempio, le operazioni di I / O come le letture del disco o della rete non sono vincolate dal GIL, quindi possono essere eseguite liberamente nei propri thread. Ma qualsiasi cosa sia multithreading che legata alla CPU è un problema.

Per i programmatori Python, ciò significa che le attività di calcolo pesanti che traggono vantaggio dall'essere distribuite su più core non vengono eseguite correttamente, salvo l'uso di una libreria esterna. La comodità di lavorare in Python ha un notevole costo in termini di prestazioni, che sta diventando sempre più difficile da digerire man mano che vengono alla ribalta linguaggi più veloci e altrettanto convenienti come Google Go.

Forzare la serratura

Nel tempo, sono emerse una serie di opzioni che migliorano, ma non eliminano, i limiti del GIL. Una tattica standard è lanciare più istanze di CPython e condividere il contesto e lo stato tra di loro; ogni istanza viene eseguita indipendentemente dall'altra in un processo separato. Ma come spiega Jeff Knupp, i guadagni forniti dalla corsa in parallelo possono essere persi dallo sforzo necessario per condividere lo stato, quindi questa tecnica è più adatta alle operazioni di lunga durata che mettono in comune i loro risultati nel tempo.

Le estensioni C non sono vincolate dal GIL, quindi molte librerie per Python che richiedono velocità (come la libreria matematica e statistica Numpy) possono essere eseguite su più core. Ma le limitazioni in CPython stesso rimangono. Se il modo migliore per evitare il GIL è usare C, ciò spingerà più programmatori lontano da Python e verso C.

PyPy, la versione di Python che compila il codice tramite JIT, non elimina il GIL ma lo compensa semplicemente facendo eseguire il codice più velocemente. In un certo senso questo non è un cattivo sostituto: se la velocità è la ragione principale per cui stai guardando al multithreading, PyPy potrebbe essere in grado di fornire la velocità senza le complicazioni del multithreading.

Infine, lo stesso GIL è stato rielaborato in qualche modo in Python 3, con un migliore gestore di commutazione dei thread. Ma tutte le sue ipotesi di fondo - e i limiti - rimangono. C'è ancora un GIL, e regge ancora il procedimento.

No GIL? Nessun problema

Nonostante tutto ciò, la ricerca di un Python senza GIL, compatibile con le applicazioni esistenti, continua. Altre implementazioni di Python hanno eliminato completamente il GIL, ma a un costo. Jython, ad esempio, viene eseguito sulla JVM e utilizza il sistema di tracciamento degli oggetti della JVM invece del GIL. IronPython adotta lo stesso approccio tramite CLR di Microsoft. Ma entrambi soffrono di prestazioni incoerenti e talvolta funzionano molto più lentamente di CPython. Inoltre, non possono interfacciarsi prontamente con codice C esterno, quindi molte applicazioni Python esistenti non funzioneranno.

PyParallel, un progetto creato da Trent Nelson di Continuum Analytics, è un "fork sperimentale e proof-of-concept di Python 3 progettato per sfruttare in modo ottimale più core della CPU". Non rimuove il GIL, ma ne migliora l'impatto sostituendo il asyncmodulo, quindi le app che utilizzano  asyncper il parallelismo (come l'I / O multithread come un server web) ne traggono vantaggio. Il progetto è rimasto inattivo per diversi mesi, ma la sua documentazione afferma che i suoi sviluppatori si sentono a proprio agio nel prendere il loro tempo per farlo bene, quindi può eventualmente essere incluso in CPython: "Non c'è niente di sbagliato in lento e costante fintanto che stai nella giusta direzione. "

Un progetto di lunga durata dei creatori di PyPy è stata una versione di Python che utilizza una tecnica chiamata "memoria transazionale software" (PyPy-STM). Il vantaggio, secondo i creatori di PyPy, è "puoi apportare piccole modifiche ai tuoi programmi esistenti, non multi-thread e farli utilizzare più core".

PyPy-STM suona come una magia, ma ha due inconvenienti. In primo luogo, è un lavoro in corso che attualmente supporta solo Python 2.x, e in secondo luogo, subisce ancora un calo delle prestazioni per le applicazioni in esecuzione su un singolo core. Poiché una delle disposizioni citate dal creatore di Python Guido van Rossum per qualsiasi tentativo di rimuovere il GIL da CPython è che la sua sostituzione non dovrebbe degradare le prestazioni per applicazioni single-core, single-threaded, una correzione come questa non atterrerà in CPython nel suo stato attuale.

Sbrigati e aspetta

Larry Hastings, uno sviluppatore principale di Python, ha condiviso alcune delle sue opinioni al PyCon 2016 su come il GIL potrebbe essere rimosso. Hastings ha documentato i suoi tentativi di rimuovere il GIL e così facendo si è ritrovato con una versione di Python che non aveva GIL, ma ha funzionato in modo estremamente lento a causa dei continui errori di cache.

Puoi perdere il GIL, ha riassunto Hastings, ma devi avere un modo per garantire che solo un thread alla volta stia modificando gli oggetti globali, ad esempio facendo in modo che un thread dedicato nell'interprete gestisca tali cambiamenti di stato.

Una buona notizia a lungo termine è che se e quando CPython abbandonerà il GIL, gli sviluppatori che utilizzano il linguaggio saranno già pronti a sfruttare il multithreading. Molte modifiche ora integrate nella sintassi di Python, come le code e le parole chiave async/ awaitper Python 3.5, semplificano la ripartizione delle attività tra i core ad alto livello.

Tuttavia, la quantità di lavoro necessaria per rendere Python senza GIL tutto ma garantisce che verrà visualizzato per primo in un'implementazione separata come PyPy-STM. Coloro che vogliono provare un sistema GIL-less possono farlo attraverso un tale sforzo di terze parti, ma è probabile che il CPython originale rimanga intatto per ora. Speriamo che l'attesa non sia molto più lunga.