इंटरप्रेटर द्वारा बनाए गए पूर्णांक कैश के साथ क्या है?


85

अजगर के स्रोत कोड में गोता करने के बाद, मुझे लगता है कि यह की एक सरणी का कहना है यह पता लगाने PyInt_Objectसे लेकर रों int(-5)को int(256)(@ src / वस्तुओं / intobject.c)

थोड़ा सा प्रयोग इसे साबित करता है:

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

लेकिन अगर मैं उन कोड को एक py फ़ाइल में एक साथ चलाता हूं (या उन्हें अर्ध-कॉलोन के साथ जोड़ देता हूं), तो परिणाम अलग है:

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

मैं उत्सुक हूं कि वे अभी भी एक ही वस्तु क्यों हैं, इसलिए मैं वाक्यविन्यास के पेड़ और संकलक में गहरी खुदाई करता हूं, मैं नीचे सूचीबद्ध एक कॉलिंग पदानुक्रम के साथ आया हूं:

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

तब मैंने कुछ डीबग कोड PyInt_FromLongऔर उसके पहले / बाद में जोड़े PyAST_FromNode, और एक test.py निष्पादित किया:

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

आउटपुट जैसा दिखता है:

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

इसका मतलब है कि के दौरान cstकरने के लिए astबदलने के दो अलग-अलग PyInt_Objectरों बनाई गई हैं (वास्तव में यह में प्रदर्शन किया है ast_for_atom()समारोह), लेकिन वे बाद में विलय कर रहे हैं।

मैं इसमें स्रोत को समझने के लिए मुश्किल लगता है PyAST_Compileऔर PyEval_EvalCode, इसलिए मैं यहाँ हूँ मदद के लिए पूछना, मैं सराहना करता है, तो कुछ एक एक संकेत देता है हो जाएगा?


2
क्या आप केवल यह समझने की कोशिश कर रहे हैं कि पायथन स्रोत कैसे काम करता है, या आप यह समझने की कोशिश कर रहे हैं कि पायथन में लिखे कोड के लिए अपशॉट क्या है? क्योंकि पायथन में लिखे गए कोड के लिए upshot "यह एक कार्यान्वयन विवरण है, कभी भी ऐसा होने या न होने पर भरोसा न करें"।
ब्रेनबार

मैं कार्यान्वयन विस्तार पर भरोसा नहीं करने जा रहा हूं। मैं बस उत्सुक हूं और स्रोत कोड में तोड़ने की कोशिश करता हूं।
felix021


@ बालकृष्ण धन्यवाद मुझे उस प्रश्न का उत्तर ज्ञात है, और मैं इससे भी आगे जाता हूं।
felix021

जवाबों:


106

अजगर रेंज में पूर्णांक कैश करता है [-5, 256], इसलिए यह उम्मीद की जाती है कि उस सीमा में पूर्णांक भी समान हैं।

आप जो देखते हैं वह समान पाठ के भाग के समान शाब्दिक रूप से अनुकूलन करने वाले पायथन कंपाइलर है।

जब पायथन शेल में टाइप करना प्रत्येक पंक्ति एक पूरी तरह से अलग स्टेटमेंट है, एक अलग पल में पार्स किया जाता है, इस प्रकार:

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

लेकिन अगर आप एक ही कोड को फाइल में रखते हैं:

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

ऐसा तब होता है जब पार्सर के पास यह विश्लेषण करने का मौका होता है कि शाब्दिक उपयोग कहां किया जाता है, उदाहरण के लिए जब इंटरएक्टिव इंटरप्रेटर में किसी फ़ंक्शन को परिभाषित करते हैं:

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

ध्यान दें कि संकलित कोड में किसके लिए एक एकल स्थिरांक है 257

अंत में, पायथन बाइटकोड कंपाइलर बड़े पैमाने पर अनुकूलन (जैसे कि टाइप की गई भाषाएं) करने में सक्षम नहीं है, लेकिन यह आपके विचार से अधिक करता है। इनमें से एक चीज़ शाब्दिक उपयोग का विश्लेषण करना और उन्हें नक़ल करने से बचना है।

ध्यान दें कि इसका कैश से कोई लेना-देना नहीं है, क्योंकि यह फ्लोट के लिए भी काम करता है, जिसमें कैश नहीं है:

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

अधिक जटिल शाब्दिकों के लिए, टुपल्स की तरह, यह "काम नहीं करता":

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

लेकिन टुपल के अंदर का शाब्दिक हिस्सा है:

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

(ध्यान दें कि निरंतर तह और peephole ऑप्टिमाइज़र बगफिक्स संस्करणों के बीच भी व्यवहार को बदल सकता है, इसलिए जो उदाहरण वापस आते हैं Trueया Falseमूल रूप से मनमाना है और भविष्य में बदल जाएगा)।


इस बारे में कि आप क्यों देखते हैं कि दो PyInt_Objectबनाए गए हैं, मुझे लगता है कि यह शाब्दिक तुलना से बचने के लिए किया गया है। उदाहरण के लिए, संख्या 257कई शाब्दिक रूप से व्यक्त की जा सकती है:

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

पार्सर के पास दो विकल्प हैं:

  • पूर्णांक बनाने से पहले शाब्दिक को कुछ सामान्य आधार में परिवर्तित करें, और देखें कि क्या शाब्दिक समकक्ष हैं। फिर एक पूर्णांक ऑब्जेक्ट बनाएँ।
  • पूर्णांक ऑब्जेक्ट बनाएँ और देखें कि क्या वे समान हैं। यदि हाँ, तो केवल एक ही मान रखें और इसे सभी शाब्दिकों को निर्दिष्ट करें, अन्यथा, आपके पास पहले से ही असाइन करने के लिए पूर्णांक हैं।

संभवतः पायथन पार्सर दूसरे दृष्टिकोण का उपयोग करता है, जो रूपांतरण कोड को फिर से लिखने से बचता है और यह भी विस्तार करना आसान है (उदाहरण के लिए यह फ्लोट के साथ भी काम करता है)।


Python/ast.cफ़ाइल को पढ़ना , वह फ़ंक्शन जो सभी नंबरों को पार्स करता है parsenumber, जो PyOS_strtoulपूर्णांक मान प्राप्त करने के लिए कॉल करता है (अंतः के लिए) और अंततः कॉल करता है PyLong_FromString:

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

जैसा कि आप यहाँ देख सकते हैं कि पार्सर यह जाँच नहीं करता है कि क्या यह पहले से ही दिए गए मान के साथ पूर्णांक पाया है और इसलिए यह बताता है कि आप क्यों देखते हैं कि दो अंतरंग वस्तुएं बनाई गई हैं, और इसका मतलब यह भी है कि मेरा अनुमान सही था: पार्सर सबसे पहले स्थिरांक बनाता है और उसके बाद ही बाइटकोड को बराबर स्थिरांक के लिए एक ही वस्तु का उपयोग करने के लिए अनुकूलित करता है।

कोड है कि इस जांच करता है में कहीं होना चाहिए Python/compile.cया Python/peephole.c, के बाद से इन फ़ाइलों कि बाईटकोड में एएसटी को बदलने हैं।

विशेष रूप से, compiler_add_oफ़ंक्शन ऐसा लगता है जो इसे करता है। इस टिप्पणी में है 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;

तो ऐसा लगता है कि compiler_add_oफ़ंक्शंस / लैम्ब्डा आदि के लिए स्थिरांक डालने के लिए उपयोग किया जाता है। compiler_add_oफ़ंक्शन स्थिरांक को एक dictवस्तु में संग्रहीत करता है , और इस से तुरंत इस बात का अनुसरण करता है कि समान स्थिरांक एक ही स्लॉट में गिर जाएंगे, जिसके परिणामस्वरूप अंतिम बाइटकोड में एक निरंतर होता है।


धन्यवाद। मुझे पता है कि इंट्रिप्टर ऐसा क्यों करता है, और मैंने पहले भी स्ट्रिंग्स का परीक्षण किया है, जो इंट और फ्लोट के समान ही कार्य करता है, और मैंने कंपाइलर ट्री का भी उपयोग किया है जो कंपाइलर का उपयोग करता है। () जो दो कॉन्स्ट (257) दिखाता है। मैं सोच रहा था कि स्रोत कोड में कब और कैसे ... ऊपर मैंने जो परीक्षण किया है उससे पता चलता है कि इंट्रिप्टर ने पहले ही a और b के लिए दो PyInt_Object बनाए थे, इसलिए वास्तव में उनका विलय (मेमोरी को बचाने के अलावा) बहुत कम अर्थ है।
felix021

@ felix021 मैंने अपना उत्तर फिर से अपडेट किया है। मैंने पाया कि दो ints कहाँ बनाए गए हैं और मुझे पता है कि ऑप्टिमाइज़ेशन किन फ़ाइलों में होती है, हालाँकि मुझे अभी भी कोड की सटीक लाइन नहीं मिली है जो इसे संभालती है।
बकुरीउ

बहुत बहुत धन्यवाद! मैंने ध्यान से कंपाइल किया। कॉलिंग चेन compiler_visit_stmt है -> VISIT (c, expr, e) -> compiler_visit_expr (c, e) -> ADDOP_O (c, LOAD_CONST, e-> v.Num.n, const) -> 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 () में, python if-not-find-then-set-PyTuple (PyIntObject n, PyInt_Type) को c-> u- u_consts में कुंजी के रूप में इस्तेमाल करने की कोशिश करेगा, और उस ट्यूपल के हैश की गणना करते समय, केवल वास्तविक int मान का उपयोग किया जाता है, इसलिए केवल एक ही Py_nt_Object को u_consts तानाशाही में डाला जाएगा।
felix021

मुझे win7 पर py2 और py3 दोनों के साथ Falseनिष्पादन मिल रहा हैa = 5.0; b = 5.0; print (a is b)
zhangxaochen

1
@zhangxaochen क्या आपने एक ही लाइन पर या इंटरएक्टिव इंटरप्रेटर में अलग-अलग लाइनों पर दो बयान लिखे थे? वैसे भी, अजगर के विभिन्न संस्करण अलग व्यवहार का उत्पादन कर सकते हैं। मेरी मशीन पर यह करता है में परिणाम True(अभी फिर से जांचा)। ऑप्टिमाइज़ेशन विश्वसनीय नहीं हैं क्योंकि वे केवल एक कार्यान्वयन विवरण हैं, जिससे मैं उस बिंदु को अमान्य नहीं करता, जिसे मैं अपने उत्तर में बनाना चाहता था। यह भी compile('a=5.0;b=5.0', '<stdin>', 'exec')).co_constsदर्शाता है कि केवल एक 5.0स्थिर (लिनक्स पर python3.3 में) है।
बाकूरी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.