2013-03-02 16 views
39

Dopo tuffo nel codice sorgente di Python, scopro che mantiene una serie di PyInt_Object s che vanno da int (-5) a int (256) (@ src/Oggetti/intobject.c)Cosa c'entra con la cache Integer in Python?

Un piccolo esperimento dimostra iT:

>>> a = 1 
>>> b = 1 
>>> a is b 
True 
>>> a = 257 
>>> b = 257 
>>> a is b 
False 

Ma se corro coloro codice insieme in un file PY (o unirsi a loro con punti e virgola), il risultato è diverso:

>>> a = 257; b = 257; a is b 
True 

sono curioso di sapere il motivo per cui sono ancora lo stesso oggetto, quindi approfondisco l'albero della sintassi e comp iler, mi si avvicinò con una gerarchia di chiamata elencati di seguito:

PyRun_FileExFlags() 
    mod = PyParser_ASTFromFile() 
     node *n = PyParser_ParseFileFlagsEx() //source to cst 
      parsetoke() 
       ps = PyParser_New() 
       for (;;) 
        PyTokenizer_Get() 
        PyParser_AddToken(ps, ...) 
     mod = PyAST_FromNode(n, ...) //cst to ast 
    run_mod(mod, ...) 
     co = PyAST_Compile(mod, ...) //ast to CFG 
      PyFuture_FromAST() 
      PySymtable_Build() 
      co = compiler_mod() 
     PyEval_EvalCode(co, ...) 
      PyEval_EvalCodeEx() 

poi ho aggiunto un po 'di codice di debug in PyInt_FromLong e prima/dopo PyAST_FromNode, ed eseguito un test.py:

a = 257 
b = 257 
print "id(a) = %d, id(b) = %d" % (id(a), id(b)) 

gli sguardi di uscita come:

DEBUG: before PyAST_FromNode 
name = a 
ival = 257, id = 176046536 
name = b 
ival = 257, id = 176046752 
name = a 
name = b 
DEBUG: after PyAST_FromNode 
run_mod 
PyAST_Compile ok 
id(a) = 176046536, id(b) = 176046536 
Eval ok 

ciò significa che durante la cst a ast trasformare, due differenti PyInt_Object s vengono creati (in realtà viene eseguito nella funzione ast_for_atom()), ma successivamente vengono uniti.

Ho difficoltà a comprendere la fonte in PyAST_Compile e PyEval_EvalCode, quindi sono qui per chiedere aiuto, sarò riconoscente se qualcuno dà un suggerimento?

+1

Stai solo cercando di capire come funziona il sorgente Python o stai cercando di capire qual è il risultato del codice scritto in Python? Poiché il risultato del codice scritto in Python è "questo è un dettaglio dell'implementazione, non contare mai sul fatto che ciò accada o non accada". – BrenBarn

+0

Non ho intenzione di fare affidamento sui dettagli di implementazione. Sono solo curioso e cerco di entrare nel codice sorgente. – felix021

+0

Correlato: [Python "è" l'operatore si comporta in modo imprevisto con numeri interi] (http://stackoverflow.com/questions/306313/python-is-operator-behaves-unexpectedly-with-integers) – Blckknght

risposta

49

Python memorizza nella cache gli interi nell'intervallo [-5, 256], pertanto è previsto che anche gli interi in tale intervallo siano identici.

Quello che vedi è il compilatore Python che ottimizza i letterali identici quando fa parte dello stesso testo.

Quando si digita nel Python sborsare ogni riga è una dichiarazione completamente diversa, analizzato in un momento diverso, in tal modo:

>>> a = 257 
>>> b = 257 
>>> a is b 
False 

Ma se si mette lo stesso codice in un file:

$ echo 'a = 257 
> b = 257 
> print a is b' > testing.py 
$ python testing.py 
True 

Questo accade ogni volta che il parser ha la possibilità di analizzare dove vengono utilizzati i letterali, ad esempio quando si definisce una funzione nell'interprete interattivo:

>>> def test(): 
...  a = 257 
...  b = 257 
...  print a is b 
... 
>>> dis.dis(test) 
    2   0 LOAD_CONST    1 (257) 
       3 STORE_FAST    0 (a) 

    3   6 LOAD_CONST    1 (257) 
       9 STORE_FAST    1 (b) 

    4   12 LOAD_FAST    0 (a) 
      15 LOAD_FAST    1 (b) 
      18 COMPARE_OP    8 (is) 
      21 PRINT_ITEM   
      22 PRINT_NEWLINE  
      23 LOAD_CONST    0 (None) 
      26 RETURN_VALUE   
>>> test() 
True 
>>> test.func_code.co_consts 
(None, 257) 

Nota come il codice compilato contiene una singola costante per 257.

In conclusione, il compilatore bytecode Python non è in grado di eseguire massicce ottimizzazioni (come i linguaggi di tipi statici), ma fa più di quanto si pensi. Una di queste cose è analizzare l'uso dei letterali ed evitare di duplicarli.

Si noti che questo non ha a che fare con la cache, perché funziona anche per i carri, che non hanno una cache:

>>> a = 5.0 
>>> b = 5.0 
>>> a is b 
False 
>>> a = 5.0; b = 5.0 
>>> a is b 
True 

Per letterali più complessi, come le tuple, "non lo fa lavoro ":

>>> a = (1,2) 
>>> b = (1,2) 
>>> a is b 
False 
>>> a = (1,2); b = (1,2) 
>>> a is b 
False 

Ma i letterali all'interno della tupla sono condivisi:

>>> a = (257, 258) 
>>> b = (257, 258) 
>>> a[0] is b[0] 
False 
>>> a[1] is b[1] 
False 
>>> a = (257, 258); b = (257, 258) 
>>> a[0] is b[0] 
True 
>>> a[1] is b[1] 
True 

Per quanto riguarda il motivo per cui vengono creati due PyInt_Object, vorrei indovinare per evitare un confronto letterale. per esempio, il numero 257 può essere espressa da più letterali:

>>> 257 
257 
>>> 0x101 
257 
>>> 0b100000001 
257 
>>> 0o401 
257 

Il parser ha due scelte:

  • convertire i letterali in qualche base comune prima di creare il numero intero, e vedere se i letterali sono equivalente. quindi creare un singolo oggetto intero.
  • Creare gli oggetti interi e vedere se sono uguali. Se sì, mantieni solo un singolo valore e assegnalo a tutti i valori letterali, altrimenti hai già gli interi da assegnare.

Probabilmente il parser Python utilizza il secondo approccio, che evita di riscrivere il codice di conversione ed è anche più semplice da estendere (ad esempio funziona anche con i float).


Lettura del file Python/ast.c, la funzione che analizza tutti i numeri è parsenumber, che chiama PyOS_strtoul per ottenere il valore intero (per intgers) e alla fine chiama PyLong_FromString:

x = (long) PyOS_strtoul((char *)s, (char **)&end, 0); 
    if (x < 0 && errno == 0) { 
     return PyLong_FromString((char *)s, 
           (char **)0, 
           0); 
    } 

Come si può vedere qui il parser fa non controlla se ha già trovato un intero con il valore dato e questo spiega perché vedi che due oggetti int sono creati, e questo significa anche che la mia ipotesi era corretta: il parser t crea le costanti e solo successivamente ottimizza il bytecode per utilizzare lo stesso oggetto per costanti uguali.

Il codice che esegue questo controllo deve essere da qualche parte in Python/compile.c o Python/peephole.c, poiché questi sono i file che trasformano l'AST in bytecode.

In particolare, la funzione compiler_add_o sembra quella che lo fa. C'è questo commento in compiler_lambda:

/* Make None the first constant, so the lambda can't have a 
    docstring. */ 
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0) 
    return 0; 

Così sembra compiler_add_o viene utilizzato per inserire costanti per le funzioni/lambda, ecc La funzione compiler_add_o memorizza le costanti in un oggetto dict, e da questo consegue che subito uguali costanti cadrà nello stesso slot, risultando in una singola costante nel bytecode finale.

+0

Grazie. So perché l'interprete fa questo, e ho anche provato le stringhe prima, che agisce proprio come int e float, e ho anche stampato l'albero della sintassi usando compiler.parse() che mostra due Const (257). Mi chiedo solo quando e come nel codice sorgente ... Inoltre, il test che ho fatto sopra mostra che l'interprete ha già creato due PyInt_Object per aeb, quindi in realtà c'è poco significato che li unisca (a parte salvare la memoria). – felix021

+0

@ felix021 Ho aggiornato di nuovo la mia risposta. Ho trovato dove vengono creati i due ints e so in quali file avviene l'ottimizzazione, anche se non ho ancora trovato la riga esatta del codice che la gestisce. – Bakuriu

+0

Grazie mille! Ho attentamente esaminato compile.c, la catena di chiamate è compiler_visit_stmt -> VISIT (c, expr, e) -> compiler_visit_expr (c, e) -> ADDOP_O (c, LOAD_CONST, e-> v.Num.n, consts) -> compiler_addop_o (c, LOAD_CONSTS, c-> u-> u_consts, e-> v.Num.n) -> compiler_add_o (c, c-> u-> u_consts, e-> v.Num.n). nel compoler_add_o(), python proverà a PyTuple if-not-find-then-set (PyIntObject n, PyInt_Type) come chiave in c-> u-> u_consts e mentre calcola l'hash di quella tupla, solo l'int effettivo viene utilizzato il valore, quindi verrà inserito solo un PyInt_Object nel formato u_consts. – felix021