di  -  giovedì 3 novembre 2011

Dopo un’occhiata al funzionamento “di massima” della macchina virtuale implementata con CPython e analizzato in dettaglio le azioni che scattano nel momento in cui abbiamo deciso di utilizzare il valore memorizzato nella variabile locale per farci qualcosa, entriamo nel concreto con l’esempio visto inizialmente e che riguarda una semplice somma.

Il blocco di codice che in CPython (ricordo che parliamo della versione 2.6.4, ma il discorso è valido per le versioni 2.x e, per buona parte, per le 3.x) è deputato all’espletamento dell’operazione di “addizione” si presenta decisamente imponente rispetto a quanto visto finora, e mette anche un po’ soggezione:

case BINARY_ADD:
  w = POP();
  v = TOP();

  if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) {
    /* INLINE: int + int */
    register long a, b, i;
    a = PyInt_AS_LONG(v);
    b = PyInt_AS_LONG(w);
    i = a + b;
    if ((i^a) < 0 && (i^b) < 0)
      goto slow_add;
    x = PyInt_FromLong(i);
  }

  else if (PyString_CheckExact(v) &&
           PyString_CheckExact(w)) {
    x = string_concatenate(v, w, f, next_instr);
    /* string_concatenate consumed the ref to v */
    goto skip_decref_vx;
  }

  else {
    slow_add:
      x = PyNumber_Add(v, w);
  }

  Py_DECREF(v);
 skip_decref_vx:
  Py_DECREF(w);
  SET_TOP(x);
  if (x != NULL) continue;
  break;

Ho provveduto ad aggiungere delle righe vuote per separare logicamente il codice in parti che hanno ognuna delle precise responsabilità, in modo da semplificarne la trattazione.

La somma è un’operazione binaria, per cui per poter lavorare e produrre il risultato ha ovviamente bisogno di due operandi, che vengono prelevati dallo stack (CPython implementa una virtual machinestack-based“) tramite la macro POP che risulta molto semplice (d’altra parte è la simmetrica della PUSH che abbiamo visto in precedenza):

#define POP()  (*--stack_pointer)

Quindi lo stack che Python usa per memorizzare le variabili locali e i risultati intermedi passerebbe da:

 -------
3|  y  | <---
 -------
2|  x  |
 -------
1|  y  |
 -------
0|  x  |
 -------

a:

 -------
1|  y  | <---
 -------
0|  x  |
 -------

Infatti questa era la situazione iniziale in cui si presenta appena prima di eseguire la prima istruzione bytecode, mentre quella precedente riflette le due operazioni LOAD_FAST che hanno inserito nello stack rispettivamente il contenuto della variabile python x e della y, come visto nel precedente articolo.

Il primo POP rimuove il valore di y (che si trova in cima) e lo pone nella variabile w, mentre il secondo fa lo stesso, ma per il valore di x che finisce in v.

v e w sono due variabili di comodo (puntatori a strutture di tipo PyObject) definite all’interno della funzione che esegue il ciclo principale della VM, e che vengono utilizzate per gli scopi più disparati.

La seconda parte dimostra la natura dinamica di Python: abbiamo due valori che sono “istanze” di una qualche “classe”, e in questo contesto dobbiamo capire cosa fare quando incontriamo il simbolo +, che non serve esclusivamente per sommare numeri, com’è evidenziato già dal primo if.

Python mette a disposizione diversi tipi di dati nativi e consente anche di crearne altri permettendo, tra l’altro, di poter definire cosa fare per determinate operazioni come la “somma” (il classico concetto di operator overloading, che suona sicuramente meglio di “sovraccarico degli operatori”).

Nello specifico, una delle operazioni più frequenti è proprio la somma fra interi (tipi “primitivi” / predefiniti di Python), ed è il motivo per cui il primo controllo verte proprio su di essi:

#define PyInt_CheckExact(op) ((op)->ob_type == &PyInt_Type)

Pur non essendo scritta tutta in maiuscolo, com’è abitudine fare in C in questi casi, PyInt_CheckExact è una macro che agisce su un puntatore a una struttura PyObject, e restituisce un valore booleano.

Sappiamo già che tutti gli oggetti di CPython sono costituiti da una struttura “base” (PyObject) che definisce due campi chiamati ob_ref e ob_type, che rappresentano il numero di volte che l’oggetto risulta “referenziato” e il tipo dell’oggetto (sotto forma di puntatore a un’ulteriore struttura dati).

In pratica per poter dire che un oggetto è di un certo tipo o, equivalentemente, che appartiene a una certa classe, si rende necessario legarlo a essa in qualche modo, ed è quello che fa il campo ob_type, appunto.

Poiché in Python anche i tipi e le classi (che sono esattamente la stessa cosa) sono oggetti, è facile intuire che ob_type sia un puntatore a… PyObject.

Infatti PyInt_Type è una variabile (opportunamente inizializzata per “modellare” la classe degli interi) in realtà di tipo PyTypeObject, che però a sua volta “discende” da PyObject. Non ne riporto la definizione perché non serve allo scopo del presente articolo, ma in futuro salterà fuori e sarà necessario per capire alcuni aspetti di più basso livello di CPython.

Al momento, quindi, prendiamo per buono che PyInt_Type rappresenti la struttura del tipo intero standard di CPython, per cui per verificare se un oggetto PyObject è di questo tipo, risulta sufficiente confrontare il suo ob_type col puntatore a PyInt_Type (espressione &PyInt_Type), esattamente come fa la macro PyInt_CheckExact.

Adesso che sappiamo distinguere se un oggetto è un tipo intero (primitivo) di CPython oppure no, possiamo procedere a esaminare il rimanente pezzo di codice della seconda parte (che si occupa della somma vera e propria di due interi) se la condizione è verificata per entrambe le variabili v e w, come vedremo meglio nel prossimo articolo.

15 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    banryu
     scrive: 

    Interessantissimo ma… azz! Ti sei fermato proprio sul più bello!
    Sta serie di articoli è come un giallo a puntate :D

    Non ti posso neanche chiedere perchè string_concatenate prende come argomenti oltre a ‘v’ e ‘w’ anche ‘f’ (e che è?) next_instr (perchè ne ha bisogno)?

    Resto in trepidante attesa del proseguio!

    P.S.: hai più provato a fare quei test di performance sulla rimuovendo l’if che verifica la presenza di un UnboundLocalError?

  • # 2
    Cesare Di Mauro (Autore del post)
     scrive: 

    No, purtroppo ho avuto altro da fare, ma è una cosa che m’interessa per cui appena rimetterò mano alla VM sarà la prima modifica che proverò (però soltanto per togliermi questa soddisfazione: ho riflettuto molto, e per com’è fatto Python purtroppo non ci sono altre strade per eliminare del tutto questo controllo).

    Riguardo alla concatenazione delle stringhe, se vedessi com’è fatta string_concatenate capiresti perché ho accuratamente evitato anche di citarla al momento. :D Ma penso comunque di scriverci qualcosa, perché di recente è venuto alla ribalta quest’argomento nella mailing-list di Python, e se n’è parlato abbastanza. Sarà utile anche per capire quali soluzioni è meglio percorrere per certe problematiche comuni, in modo da non dipendere troppo dall’implementazione (attuale) della VM. C’è parecchia carne al fuoco, come puoi immaginare.

    Infine sul “giallo” dell’interruzione. Sì, è vero che lascio l’argomento in sospeso, ma t’assicuro che proseguire approfondendo tutto il resto (a parte la concatenazione delle stringhe, per la quale vedi sopra) avrebbe appesantito parecchio l’articolo, che è già abbastanza tecnico per quello di cui stiamo parlando.

    Alla fine una manciata di righe di codice non dovrebbe spaventare così tanto, però ci sono due chiamate a funzioni in mezzo, e non hai idea di cosa salterà fuori parlando della PyInt_FromLong (peggio ancora quando parlerò appositamente della PyNumber_Add che è la funzione che si occupa di “sbrogliare la matassa” della “somma” per TUTTI i tipi di dati gestiti da Python, inclusi quelli creati dagli utenti).

    Diciamo che ho preferito lasciare per dopo le parti più critiche e troppo tecniche, quelle che dal mondo (C)Python passano a quello del C e, soprattutto, il viceversa. Sempre parlando di “interi” (per ora :D).

    Purtroppo dovendo tenere principalmente un taglio divulgativo devo necessariamente scendere a qualche compromesso. :|

  • # 3
    Tommaso
     scrive: 

    Però sapere che ogni volta che scrivo un innocente “+” il computer deve sorbirsi tutto questo codice, mi fa vedere in un’ottima luce C++ :P

    una domanda a tradimento: ma come mai, di tutti i linguaggi interpretati più famosi, ormai solo Python è rimasto senza JIT? Problemi strutturali di complessità?

  • # 4
    Cesare Di Mauro (Autore del post)
     scrive: 

    Python ha una VM abbastanza complessa (il linguaggio è molto ricco e offre diversi costrutti sintattici e tipi).

    Google c’ha provato col progetto Unladen Swallow, ma a causa dell’approccio (hanno usato LLVM) diciamo che è fallito (si portati dentro più problemi di quelli che avrebbero dovuto risolvere).

    Comunque nessuno di CPython pensa a un JIT, perché c’è già PyPy che lo implementa con eccellenti risultati. Inutile buttare risorse quando c’è già un progetto che è ormai molto maturo e che viene visto come la futura versione “mainstream”.

    Per il resto sì, paragonandolo a un linguaggio staticamente tipato fa venire il mal di testa pensando a tutta la roba che c’è dietro. Però Python ha i suoi vantaggi in termini di produttività. ;)

  • # 5
    homero
     scrive: 

    spero che tutta questa produttività ci sia….poi non mi venite a dire che il c è obsoleto…..
    quel goto pero’ proprio non riesco a buttarlo giu’….ma è proprio necessario?

  • # 6
    Cesare Di Mauro (Autore del post)
     scrive: 

    I goto cerco sempre di evitarli, ma purtroppo in CPython ce ne sono diversi. L’alternativa c’è ovviamente, ma sarebbe necessario duplicare pezzi di codice e/o introdurre altre variabili.

    Poiché il ciclo principale di una VM deve essere quanto più ottimizzato possibile (meno codice = miglior uso della cache), purtroppo vedo che chi ha scritto CPython non s’è fatto tanti scrupoli.

    Comunque la produttività c’è, eccome. 8-)

    Sul C, invece, preferisco non pronunciarmi. :P

  • # 7
    banryu
     scrive: 

    “Poiché il ciclo principale di una VM deve essere quanto più ottimizzato possibile (meno codice = miglior uso della cache), purtroppo vedo che chi ha scritto CPython non s’è fatto tanti scrupoli.”

    Beh, alla fine e’ il contesto a dettar legge… Questa e’ l’implementazione di un virtual machine, se la quantità di codice e’ un fattore critico il goto utilizzato a tal fine e’ piu’ che giustificato, secondo me.

  • # 8
    Cesare Di Mauro (Autore del post)
     scrive: 

    Sì, lo è sicuramente. Anche se da programmatore ogni volta che vedo un goto mi piange il cuore… -_-

  • # 9
    homero
     scrive: 

    senti io sto studiando javascript e html5 che dovrebbero essere il futuro delle interfacce utenti…….
    mi puoi dire perchè python è cosi’ produttivo?
    io vedo la piena portabilità soltanto nelle browser application legate ad un server locale o remoto che sia…..
    javascript nell’ultima revisione ecmascript 2011 non è poi cosi’ malvagio e sicuramente migliorerà ancora….
    insomma per costruirci un’interfaccia utente credo che basti e avanzi….d’altronde basta vedere quello che ha tirato fuori google con javascript…
    ora python sicuramente è migliore per mille motivi ma gira su una virtual machine da installare su cui va caricato il software….
    mentre con il browser tutto viene caricato onfly…..
    lo dico senza polemica forse sbaglio tutto io…..ed in questa maniera perdo soldi….
    un futuro articolo su come python sia produttivo e possa abbattere a tal punto i tempi di sviluppo da poter far lievitare i fatturati….

  • # 10
    Cesare Di Mauro (Autore del post)
     scrive: 

    Python non è certo la pietra filosofale dell’informatica. Come tutti gli strumenti ha pregi e difetti, ambiti d’utilizzo dove si presta di più, e altri in cui è meglio non usarlo.

    Come per tutte le cose penso sia fondamentale il buon senso che dovrebbe possedere un programmatore degno di questo nome.

    Ad esempio Python eccelle nella modellazione / prototipazione, e per questo è molto usato in ambito economico / finanziario (c’era un interessante talk alla recente conferenza EuroPython) e sta letteralmente dilagando in ambito scientifico.

    Eccoti un esempio: http://bellm.org/blog/2011/05/27/why-astronomers-should-program-in-python/

    Mi dirai: ma non c’è Matlab per la modellazione / sperimentazione? Beh, se un matematico (ZioSilvio, noto utente e moderatore del forum di hwupgrade, che probabilmente conosci) a un utente che chiede consiglio se usare Matlab o Python gli risponde testualmente “Python spacca il c*l* a Matlab” (ovviamente con le estensioni NumPy & SciPy), magari qualche dubbio che sia vero ti viene e vai a buttargli un occhio. ;)

    Riguardo al web, è vero ciò che dici. Col browser hai già HTML5 e Javascript, che sono immediatamente disponibili. Questo è un vantaggio non indifferente, ed è impossibile (oltre che insensato) negarlo.

    Personalmente è da un po’ di mesi che smanetto che Silverlight, per cui se dovessi realizzare un sito lo preferirei ad HTML5 & JavaScript. Primo perché con Silverlight ho un framework altamente produttivo che mi permette di fare tante cose in poco tempo (dai un’occhiata al suo sito). Secondo perché mi permette di utilizzare qualunque linguaggio .NET, e quindi IronPython (per te che sei “C-ofilo” ci sarebbe C#, che è anch’esso un signor linguaggio) che uso proficuamente (al momento con WPF, che è il “papà” di Silverlight) ed è molto comodo (dai un’occhiata veloce qui: http://www.voidspace.org.uk/ironpython/silverlight/index.shtml ). Il prezzo da pagare è l’installazione del plugin, ma a mio avviso è ampiamente ripagato dai benefici che offre.

    Riguardo all’ultima versione di Javascript, non ne ho letto le specifiche, per cui non so cos’hanno introdotto di recente rispetto a 4-5 anni fa (quando ho letto lo standard ECMA). Non mi piace proprio come linguaggio per la sintassi e per certe scelte discutibili che sono state fatte (prima su tutte la “definizione” di una classe). Poi è chiaro che subentra il fattore “gusti”.

    Per un confronto fra i vari linguaggi puoi dare un’occhiata a questo http://rosettacode.org/wiki/Main_Page sito.

    OK, spero di aver chiarito tutto, perché siamo abbondantemente OT e non vorrei tornare nuovamente sull’argomento. ;)

  • # 11
    Pluto
     scrive: 

    Per recuperare il valore che viene messo nella variabile di comodo “w” viene usata la macro POP. Mentre per recuperare il valore per la variabile “v” si usa la macro TOP. Perché questa differenza?

  • # 12
    Cesare Di Mauro (Autore del post)
     scrive: 

    Alla fine qualcuno se n’è accorto. :D

    Anticipo parte delle conclusioni. Il motivo è strettamente di ottimizzazione (che nella VM di Python, si sarà capito, è un mantra).

    Diciamo che in un’implementazione “da manuale” si sarebbero dovuti effettuare due POP per prelevare entrambi gli operandi, e una volta ottenuto il risultato della “somma” eseguirne il PUSH nello stack.

    Nel codice in esame, invece, si preferisce che la seconda operazione sia una TOP, che non fa altro che leggere il contenuto della cima dello stack senza però modificarne lo stato (il valore letto rimane sempre nella cima).
    Una volta ottenuto il risultato lo si sovrascriverà alla cima dello stack, come ci si aspetta, appunto, al completamento dell’operazione.

    In soldoni è una sottigliezza che fa risparmiare un’operazione di decremento e una successiva di incremento del puntatore alla cima dello stack.

  • # 13
    Pluto
     scrive: 

    Ah, volevi vedere se eravamo attenti? :)

    Un’ultima cosa, immagno che lo stack, nel momento di dell’esecuzione del bytecode sia in uso esclusivo. Non c’è nessun altro thread che ci lavora. Quindi, ho la VM è mono-thread, oppure
    da qualche parte ci deve essere una sincronizzazione sullo stack.

  • # 14
    Fabio T.
     scrive: 

    Domanda:
    se preferisci usare Silverlight al posto di javascript e html5 allora preferisci boicottare tutto ciò che non è windows o mac, indi per cui è inutile sviluppare per un browser, fai direttamente qualcos’altro…
    a mio modo di vedere sviluppare qualcosa per un browser ha significato se serve soprattutto portabilitià su qualsiasi piattaforma, e mi spiace silverlight non lo è (e non dirmi di mono che è indietro da anni ormai e nemmeno il fan microsoft di Miguel de Icaza non si rende conto del pacco che gli ha tirato la controlalta Microsoft quando hanno comprato Novell e cacciato dai progetti sostenuti)
    mi sbaglio?

  • # 15
    Cesare Di Mauro (Autore del post)
     scrive: 

    Ho espresso soltanto una preferenza da sviluppatore. Se devo realizzare un sito web e ho facoltà di scegliere gli strumenti per svilupparlo, preferirei quelli di cui sopra per una questione di comodità e produttività personali.

    Per il resto ti faccio soltanto notare, e chiudo l’OT, che Silverlight non è un’esclusiva di Windows, e che se Moonlight non ti sembra maturo (per alcuni aspetti è anche superiore a Silverlight) rimane un progetto open source a cui chiunque può contribuire per migliorarlo.

    @Pluto: no, è che avrei preferito parlarne dopo. Vabbé, ormai l’ho anticipato, e amen. :P

    Sì, la VM è Python è strettamente mono thread, per cui l’accesso allo stack, come pure ad altre strutture private e a qualunque oggetto python, è “safe”.

    Questo perché viene usato un lock (il famigerato GIL, Global Interpreter Lock) che ne regola l’accesso, che viene rilasciato ogni tanto per dare la possibilità agli altri thread di poter usufruire della CPU.

    Con Python 2.x questo lock consuma un po’ di risorse, ma a partire dalla 3.1 o 3.2 (non ricordo bene) c’è un’implementazione di gran lunga più efficiente e si sente molto meno questo continuo alternarsi di lock & release.

    C’è parecchia letteratura in merito e diversi tentativi di togliere di mezzo il GIL, ma finora rimane l’unica soluzione che in applicazioni mono-thread garantisce prestazioni nettamente superiori rispetto a qualunque altra che è stata provata finora.

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.