Yorumlayıcı tarafından tutulan tamsayı önbelleği nedir?


85

Python'un kaynak koduna daldıktan PyInt_Objectsonra int(-5), ile int(256)(@ src / Objects / intobject.c) arasında değişen bir dizi s dizisini koruduğunu öğrendim.

Küçük bir deney bunu kanıtlıyor:

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

Ancak bu kodu bir py dosyasında birlikte çalıştırırsam (veya noktalı virgüllerle birleştirirsem), sonuç farklı olur:

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

Neden hala aynı nesne olduklarını merak ediyorum, bu yüzden sözdizimi ağacına ve derleyiciye daha derine iniyorum, aşağıda listelenen bir arama hiyerarşisi buldum:

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

Sonra içine PyInt_FromLongve sonrasına bazı hata ayıklama kodları ekledim PyAST_FromNodeve bir test.py çalıştırdım:

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

çıktı şöyle görünür:

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

Bu cst, astdönüştürme sırasında iki farklı PyInt_Objects'nin yaratıldığı (aslında ast_for_atom()işlevde gerçekleştirilir ), ancak daha sonra birleştirildiği anlamına gelir.

Kaynağı anlamakta zorlanıyorum PyAST_Compileve PyEval_EvalCodebu yüzden yardım istemek için buradayım, biri bir ipucu verirse minnettar olurum?


2
Python kaynağının nasıl çalıştığını anlamaya mı çalışıyorsun yoksa Python'da yazılan kodun sonucunun ne olduğunu anlamaya mı çalışıyorsun? Çünkü Python'da yazılan kodun sonucu "bu bir uygulama ayrıntısıdır, bunun olmasına veya olmamasına asla güvenmeyin".
BrenBarn 02

Uygulama detayına güvenmeyeceğim. Sadece merak ediyorum ve kaynak koduna girmeye çalışıyorum.
felix021


@Blckknght teşekkürler. Bu sorunun cevabını biliyordum ve bunun ötesine geçiyorum.
felix021

Yanıtlar:


106

Python aralıktaki tam sayıları önbelleğe alır [-5, 256], bu nedenle bu aralıktaki tam sayıların da aynı olması beklenir.

Gördüğünüz, Python derleyicisinin aynı metnin bir parçası olduğunda aynı değişmez değerleri optimize ediyor.

Python kabuğuna yazarken, her satır tamamen farklı bir ifadedir, farklı bir anda ayrıştırılır, böylece:

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

Ancak aynı kodu bir dosyaya koyarsanız:

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

Bu, ayrıştırıcının değişmez değerlerin nerede kullanıldığını analiz etme şansı olduğunda, örneğin etkileşimli yorumlayıcıda bir işlev tanımlarken gerçekleşir:

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

Derlenen kodun 257.

Sonuç olarak, Python bayt kodu derleyicisi büyük optimizasyonlar (statik olarak yazılmış diller gibi) gerçekleştiremez, ancak düşündüğünüzden fazlasını yapar. Bunlardan biri, değişmezlerin kullanımını analiz etmek ve bunları kopyalamaktan kaçınmaktır.

Bunun önbellekle bir ilgisi olmadığını unutmayın, çünkü önbelleği olmayan kayan numaralar için de çalışır:

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

Tuples gibi daha karmaşık değişmez değerler için, "çalışmaz":

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

Ancak demet içindeki değişmez değerler paylaşılır:

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

(Sabit katlamanın ve gözetleme deliği iyileştiricisinin, hata düzeltme sürümleri arasında bile davranışı değiştirebileceğini unutmayın, bu nedenle örnekler geri döner Trueveya Falsetemelde keyfi olur ve gelecekte değişecektir).


Neden ikisinin PyInt_Objectyaratıldığını gördüğünüze gelince , bunun birebir karşılaştırmayı önlemek için yapıldığını tahmin ediyorum . örneğin, sayı 257birden çok değişmez değerle ifade edilebilir:

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

Ayrıştırıcının iki seçeneği vardır:

  • Tam sayıyı oluşturmadan önce değişmez değerleri bazı ortak tabana dönüştürün ve değişmez değerlerin eşdeğer olup olmadığına bakın. daha sonra tek bir tamsayı nesnesi oluşturun.
  • Tamsayı nesnelerini oluşturun ve eşit olup olmadıklarını görün. Evet ise, yalnızca tek bir değer tutun ve bunu tüm değişmez değerlere atayın, aksi takdirde atanacak tam sayılara zaten sahipsiniz.

Muhtemelen Python ayrıştırıcısı, dönüşüm kodunu yeniden yazmaktan kaçınan ve ayrıca genişletmesi daha kolay olan ikinci yaklaşımı kullanır (örneğin, kayan değerlerle de çalışır).


Okuma Python/ast.cdosya, tüm sayılar ayrıştırır işlevini parsenumberçağırır PyOS_strtoul(intgers için) tam sayı değeri elde etmek için sonunda çağırır PyLong_FromString:

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

Eğer ayrıştırıcı yok burada görebileceğiniz gibi değil zaten belirli bir değeri olmayan bir tamsayı bulundu ve iki int nesneleri oluşturulur görüyoruz ve benim tahminim doğru olduğunu bu da aracı neden bu kadar açıklıyor olmadığını kontrol edin: ayrıştırıcı ilk sabitleri yaratır ve ancak daha sonra bayt kodunu aynı nesneyi eşit sabitler için kullanacak şekilde optimize eder.

Bu denetimi yapan kod , AST'yi bayt koduna dönüştüren dosyalar olduğu için Python/compile.cveya içinde bir yerde olmalıdır Python/peephole.c.

Özellikle, compiler_add_oişlev bunu yapan gibi görünüyor. Şu yorum var 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;

Bu nedenle compiler_add_o, işlevler / lambdalar vb. İçin sabitler eklemek için kullanılmış gibi görünüyor . compiler_add_oİşlev, sabitleri bir dictnesneye kaydeder ve bundan hemen sonra eşit sabitlerin aynı yuvaya düşmesi sonucu son bayt kodunda tek bir sabit ortaya çıkar.


Teşekkürler. Tercümanın bunu neden yaptığını biliyorum ve daha önce de int ve float gibi davranan dizeleri test ettim ve ayrıca iki Const (257) gösteren compiler.parse () kullanarak sözdizimi ağacını da yazdırdım. Sadece kaynak kodda ne zaman ve nasıl olduğunu merak ediyorum ... Yukarıda yaptığım test, yorumlayıcının zaten a ve b için iki PyInt_Object oluşturduğunu gösteriyor, bu yüzden onları birleştirmenin aslında çok az anlamı var (hafızayı kaydetmenin dışında).
felix021

@ felix021 Cevabımı tekrar güncelledim. İki girişin nerede oluşturulduğunu buldum ve optimizasyonun hangi dosyalarda gerçekleştiğini biliyorum, yine de bunu işleyen tam kod satırını bulamadım.
Bakuriu

Çok teşekkürler! Compile.c'yi dikkatlice gözden geçirdim, çağıran zincir 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). compoler_add_o () içinde, python eğer-bulamazsa-sonra-PyTuple'ı (PyIntObject n, PyInt_Type) c-> u-> u_consts'a anahtar olarak ayarlamaya çalışır ve bu demetin hash değerini hesaplarken, yalnızca gerçek int değer kullanılır, bu nedenle u_consts diktesine yalnızca bir PyInt_Object eklenir.
felix021

Ben olsun Falseyürütme a = 5.0; b = 5.0; print (a is b)win7 üzerinde py2 ve PY3 ile hem
zhangxaochen

1
@zhangxaochen İki ifadeyi etkileşimli tercümanda aynı satıra mı yoksa farklı satırlara mı yazdınız? Her neyse, python'un farklı sürümleri farklı davranışlar üretebilir. Benim makinede o does sonuçları True(sadece şimdi tekrar kontrol). Optimizasyonlar, sadece bir uygulama detayı oldukları için güvenilir değildir, bu yüzden cevabımda belirtmek istediğim noktayı geçersiz kılmaz. Ayrıca compile('a=5.0;b=5.0', '<stdin>', 'exec')).co_constssadece bir 5.0sabit olduğunu gösterir (linux üzerinde python3.3'te).
Bakuriu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.