“Finalmente è finita”. Questa è la prima cosa che ho pensato alla chiusura della sessione di domande & risposte prevista al termine del talk di presentazione del mio progetto, come atto liberatorio dell’enorme tensione accumulata fino ad allora.
Stare “dall’altra parte” un’ora circa come oratore per uno che non si è mai cimentato in un’impresa del genere può essere fisicamente e psicologicamente molto pesante da sostenere.
A parte questo, come anticipato da Jacopo, lo scorso sabato ho tenuto un talk alla recente PyCon3 in cui ho parlato di una nuova implementazione di Python nella quale ho fatto convergere diverse mie idee su come ottimizzarla, e in particolare quella principale che riguarda l’uso di un nuovo modello di opcode, che ho chiamato wordcode…
Riuscire a sintetizzare più di tre mesi di lavoro non era possibile già per un talk di un’ora, figuriamoci per un articolo di un blog tecnologico. Per cui, seguendo più o meno la stessa strada (che non è quella della sintesi, quanto della trattazione di precisi punti), toccherò alcuni argomenti chiave senza scendere nel dettaglio.
Tornando al nuovo modello di opcode, la scelta del nome “wordcode” non è casuale, visto che suona molto simile a bytecode. Infatti la rappresentazione sotto forma di bytecode è molto utilizzata nei linguaggi che fanno uso di virtual machine (il più famoso è Java), come appunto avviene attualmente anche per Python. Io ho scelto di “andare oltre”, proponendo l’uso di word (a 16 bit).
Ovviamente cambiamenti di questo tipo non vengono realizzati soltanto per proporre qualcosa di diverso, ma perché portano a dei guadagni tangibili in qualche area. Nello specifico il solo passaggio dal bytecode al wordcode ha permesso una riduzione in termini di spazio stimata fra il 22 e il 25% per la memorizzazione degli opcode delle istruzioni.
L’introduzione di opcode ad hoc per gestire alcuni casi comuni ha permesso di ridurre ulteriormente lo spazio occupato arrivando fino a un 30% circa, e al contempo anche il numero di istruzioni eseguite (che sostanzialmente in un colpo solo eseguivano lo stesso lavoro di due o più istruzioni).
Un punto molto importante è proprio quella riduzione del numero delle istruzioni eseguite. Questo perché in virtual machine di questo tipo un fattore che fa perdere tempo e, conseguentemente, prestazioni è il ciclo principale che si occupa di eseguire il fetch e la decodifica delle medesime.
Questo ciclo purtroppo deve effettuare necessariamente dei controlli (ad esempio per verificare la presenza di eventi che fossero eventualmente “scattati” durante l’esecuzione di un opcode, e che devono essere gestiti) che penalizzano un po’ le prestazioni. Meno si riesce a tornare a eseguire il loop, e più si guadagna in velocità.
Il modello a wordcode a cui ho pensato non serviva soltanto per recuperare spazio (cosa comunque utile), ma ciò che avevo in mente era la possibilità di implementare istruzioni più complesse che riuscissero a eseguire più lavoro riducendo pertanto il tempo che la CPU passa a eseguire le suddette operazioni “di servizio” nel ciclo principale.
Sia chiaro: implementare istruzioni più complesse era fattibile già col “vecchio” modello a bytecode, ma avrebbe saturato velocemente il numero opcode disponibili (ce ne sono soltanto 256), non potendo poi permettere di implementare tutte quelle necessarie a completare l’implementazione a cui pensavo.
Con le wordcode, invece, mi è stato possibile rifattorizzatore e raggruppare opportunamente le istruzioni, e ciò ha consentito di gestire con semplicità e notevole flessibilità l’introduzione di opcode che facessero uso di operazioni unarie e binarie, che sono fra le più usate e critiche per l’esecuzione di codice in Python.
Tutto ciò mi ha permesso di introdurre delle particolari “famiglie” di istruzioni che hanno reso la mia implementazione di Python simile a un modello a registri, comunemente usato in alcune virtual machine (ad esempio quella di LUA), pur mantenendo al contempo anche il modello a stack che era quello basato sulla vecchia versione. Insomma, un modello ibrido che cerca di sfruttare i pregi dell’una e dell’altra soluzione, senza stravolgere troppo il codice.
L’introduzione di queste famiglie di istruzioni ha permesso di compattare ulteriormente il codice (che adesso occupa mediamente il 44% dello spazio in meno), ma soprattutto di ridurre notevolmente il numero di istruzioni eseguite (mediamente il 38% in meno) da eseguire per portare a termine le stesse operazioni. Meno istruzioni vuol dire meno tempo della CPU passato nel loop della VM, coi vantaggi di cui parlavo prima.
Il modello a wordcode ha permesso anche di eliminare un collo di bottiglia del ciclo di principale, rappresentato dal fatto che per ogni opcode in precedenza era necessario controllare se fosse dotato o meno di argomento, ed eventualmente prelevarlo dalla sequenza di byte. Col nuovo modello concettualmente non esistono istruzioni non dotate di parametri: tutte ne hanno almeno uno, per cui tale controllo non è più necessario, evitando al processore di incappare in branch misprediction che capitano quando la suddetta verifica fallisce.
Un’altra cosa interessante è il fatto che, leggendo word e non byte, è possibile ottimizzare il numero di operazioni di load / store della CPU, consumando al contempo meno banda di memoria verso cache e memoria centrale.
Entrambe sono possibili perché la maggior parte delle istruzioni eseguite richiedono una sola word (2 byte), anziché i 3 byte della precedente implementazione. La word viene caricata in un colpo solo (con una sola load), mentre prima erano necessarie 3 distinte operazioni di load dei singoli byte.
Ho apportato poi diverse altre ottimizzazioni che hanno permesso di occupare meno spazio e ridurre il numero di istruzioni. Le aree toccate sono molte, per cui cito le principali: constant folding (più aggressivo, efficiente, e riconosce casi anche molto complessi), salti condizionati (ottimizzati), cicli (ci sono meno istruzioni), comprehension (meno istruzioni da eseguire nel ciclo di questi costrutti sintattici).
Ovviamente non posso trattare nel dettaglio tutto, perché Appunti Digitali non è un sito di divulgazione scientifica. Comunque ho creato un progetto su Google Code, in cui trovate i sorgenti, un pacchetto con l’eseguibile per Windows, e soprattutto le slide del talk.
Capisco che l’argomento non è dei più semplici, ma in ogni caso per qualunque dubbio, osservazione, chiarimenti, critiche (costruttive, ovviamente) sono a disposizione.