2013-03-02 12 views
39

Po rozbijają kodu źródłowego Pythona, to okazuje się, że utrzymuje tablicę PyInt_Object s począwszy od int (-5) int (256) (@ src/obiektów/intobject.c)Co oznacza pamięć podręczna Integer wewnątrz Pythona?

przeprowadzając eksperyment dowodzi to:

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

Ale jeśli uruchomię tych kodu razem w pliku py (lub połączyć je ze średnikami), wynik jest inny:

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

Jestem ciekaw, dlaczego są one nadal ten sam obiekt, więc zagłębiam się głębiej w drzewo składniowe i comp ller, wpadłem hierarchii wywołującego wymienionych poniżej:

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() 

Potem dodałem trochę kodu i debugowania w PyInt_FromLong przed/po PyAST_FromNode i wykonał test.py:

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

wygląda wyjściowe jak:

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 

oznacza to, że podczas cst do ast przekształcić dwa różne PyInt_Object s są tworzone (w rzeczywistości są wykonywane w funkcji ast_for_atom()), ale później są scalane.

Trudno jest mi zrozumieć źródło w PyAST_Compile i PyEval_EvalCode, więc jestem tutaj, aby poprosić o pomoc. Będę wdzięczny, jeśli ktoś daje podpowiedź?

+1

Czy próbujesz zrozumieć, jak działa źródło Pythona, czy też próbujesz zrozumieć, jaki jest wynik kodu napisanego w Pythonie? Ponieważ wynik kodu napisanego w Pythonie brzmi: "to jest szczegół implementacji, nigdy nie polegaj na tym, co się dzieje lub nie dzieje". – BrenBarn

+0

Nie zamierzam polegać na szczegółach wdrożenia. Jestem ciekawy i próbuję włamać się do kodu źródłowego. – felix021

+0

Powiązane: [Python "jest" operator zachowuje się nieoczekiwanie z liczb całkowitych] (http://stackoverflow.com/questions/306313/python-is-operator-behaves-nieoczekiwanie-z-rozmiarów) – Blckknght

Odpowiedz

49

Python buforuje liczby całkowite z zakresu [-5, 256], więc oczekuje się, że liczby całkowite w tym zakresie są również identyczne.

To, co widzisz, to kompilator Pythona optymalizujący identyczne literały, gdy jest częścią tego samego tekstu.

Po wpisaniu w Pythonie shell każda linia jest zupełnie inna wypowiedź, analizowany w innym momencie, a więc:

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

Ale jeśli umieścisz ten sam kod do pliku:

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

Dzieje się to za każdym razem, gdy analizator składowy ma szansę przeanalizować, gdzie używane są literały, na przykład podczas definiowania funkcji w interaktywnym interprerze:

>>> 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) 

Zobacz, jak skompilowany kod zawiera pojedynczą stałą dla 257.

Podsumowując, kompilator kodu bajtowego w języku Python nie jest w stanie wykonać ogromnych optymalizacji (takich jak języki statyczne), ale robi więcej, niż myślisz. Jedną z nich jest analiza użycia literałów i unikanie ich powielania.

zauważyć, że nie ma do czynienia z pamięci podręcznej, ponieważ działa również dla pływaków, które nie mają skrzynki:

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

Dla bardziej złożonych literałów, jak krotki, że „nie praca ":

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

Ale literały wewnątrz krotki są wspólne:

>>> 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 

Jeśli chodzi o to, dlaczego widzisz, że dwa PyInt_Object zostały stworzone, powinienem zgadnąć, że robi się to, aby uniknąć dosłownego porównania. Na przykład, liczba 257 może być wyrażony przez wiele literałach:

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

Parser ma dwie możliwości:

  • Konwersja literały na typowe zasady przed utworzeniem całkowitą i sprawdzić, czy literałami są równowartość. następnie utwórz pojedynczy obiekt integer.
  • Utwórz obiekty całkowite i zobacz, czy są one równe. Jeśli tak, zachowaj tylko jedną wartość i przypisz ją do wszystkich literałów, w przeciwnym razie masz już liczby całkowite do przypisania.

Prawdopodobnie parser Pythona używa drugiego podejścia, które pozwala uniknąć przepisywania kodu konwersji, a także jest łatwiejsze do rozszerzenia (na przykład działa również z elementami pływającymi).


Odczytywanie pliku Python/ast.c, funkcja, która analizuje wszystkie numery to parsenumber, który nazywa PyOS_strtoul aby uzyskać wartość całkowitą (na intgers) i ostatecznie wywołuje PyLong_FromString:

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

Jak widać tutaj parser robi , a nie sprawdzić, czy już znalazł liczbę całkowitą o podanej wartości, a to wyjaśnia, dlaczego widzisz, że dwa obiekty int są tworzone, i to również oznacza, że ​​moje przypuszczenie było poprawne: parser jodły t tworzy stałe, a dopiero później optymalizuje kod bajtowy, aby użyć tego samego obiektu dla równych stałych.

Kod, który wykonuje tę kontrolę musi być gdzieś w Python/compile.c lub Python/peephole.c, ponieważ są to pliki, które przekształcają AST w kod bajtowy.

W szczególności, funkcja compiler_add_o wydaje się być tą, która to robi. Jest to komentarz w 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; 

więc wydaje się compiler_add_o służy do wstawiania stałych dla funkcji/lambda itp Funkcja compiler_add_o przechowuje stałe do dict obiektu, a od tego natychmiast wynika, że ​​równe stałe spadnie w tym samym gnieździe, dając w wyniku pojedynczą stałą w końcowym bajtodzie.

+0

Dzięki. Wiem, dlaczego intepreter to robi, a ja przetestowałem również wcześniej łańcuchy, które działają tak samo jak int i float, a także wydrukowałem drzewo składniowe za pomocą kompilatora.parse(), który pokazuje dwa Const (257). Zastanawiam się tylko, kiedy i jak w kodzie źródłowym ... Co więcej, test, który zrobiłem powyżej pokazuje, że intepreter stworzył już dwa PyInt_Object dla aib, więc nie ma większego sensu ich scalania (poza oszczędzaniem pamięci). – felix021

+0

@ felix021 Zaktualizowałem swoją odpowiedź ponownie. Znalazłem miejsce, w którym zostały utworzone dwa pliki int i wiem, w których plikach odbywa się optymalizacja, mimo że wciąż nie znalazłem dokładnej linii kodu, która je obsługuje. – Bakuriu

+0

Dziękuję bardzo! Ostrożnie przeszedłem compile.c, łańcuchem wywołującym jest 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). w compoler_add_o(), python spróbuje, jeśli -nie-nie-znajdzie-ustawi PyTuple (PyIntObject n, PyInt_Type) jako klucz do c-> u-> u_consts, i podczas obliczania skrótu tej krotki, tylko rzeczywiste int wartość jest używana, więc tylko jeden PyInt_Object zostanie wstawiony do dyktatury u_consts. – felix021