[python] सूची (] से तेज [] क्यों है?



Answers

list() लिए एक वैश्विक लुकअप और फ़ंक्शन कॉल की आवश्यकता होती है लेकिन [] एक ही निर्देश के लिए संकलित होती है। देख:

Python 2.7.3
>>> import dis
>>> print dis.dis(lambda: list())
  1           0 LOAD_GLOBAL              0 (list)
              3 CALL_FUNCTION            0
              6 RETURN_VALUE        
None
>>> print dis.dis(lambda: [])
  1           0 BUILD_LIST               0
              3 RETURN_VALUE        
None
Question

मैंने हाल ही में [] और list() की प्रसंस्करण गति की तुलना की और यह जानकर आश्चर्यचकित हुआ कि [] list() से तीन गुना तेज है। मैंने {} और dict() साथ एक ही परीक्षण चलाया और परिणाम व्यावहारिक रूप से समान थे: [] और {} दोनों ने 0.128sec / million चक्र लिया, जबकि list() और dict() ने लगभग 0.428sec / million चक्र प्रत्येक को लिया।

ऐसा क्यों है? क्या करें [] और {} (और शायद () और '' भी) तुरंत कुछ रिक्त स्टॉक अक्षर की एक प्रतियां पास करते हैं जबकि उनके स्पष्ट रूप से नामित समकक्ष ( list() , dict() , tuple() , str() ) पूरी तरह से एक वस्तु बनाने के बारे में जाना, चाहे वे वास्तव में तत्व हैं या नहीं?

मुझे नहीं पता कि ये दो विधियां कैसे भिन्न हैं लेकिन मुझे पता लगाना अच्छा लगेगा। मुझे दस्तावेज़ों में या SO पर कोई जवाब नहीं मिला, और खाली ब्रैकेट की खोज करने से मुझे अपेक्षाकृत अधिक समस्याग्रस्त हो गया।

मुझे timeit.timeit("[]") और timeit.timeit("list()") , और timeit.timeit("{}") और timeit.timeit("dict()") को कॉल करके मेरे समय के परिणाम मिल गए हैं, क्रमशः सूचियों और शब्दकोशों की तुलना करने के लिए। मैं पाइथन 2.7.9 चला रहा हूँ।

मैंने हाल ही में खोज की है " यदि सच है तो 1 से कम धीमा क्यों है? " जो कि if True है if 1 के प्रदर्शन की तुलना करता है और इसी तरह के शाब्दिक-बनाम-वैश्विक परिदृश्य पर स्पर्श करना प्रतीत होता है; शायद यह भी विचार करने लायक है।




यहां के जवाब बिंदु पर महान हैं और इस सवाल को पूरी तरह से कवर करते हैं। मैं दिलचस्पी रखने वालों के लिए बाइट-कोड से एक और कदम नीचे छोड़ दूंगा। मैं सीपीथॉन का सबसे हालिया रेपो का उपयोग कर रहा हूं; पुराने संस्करण इस संबंध में समान व्यवहार करते हैं लेकिन मामूली परिवर्तन हो सकते हैं।

इनमें से प्रत्येक के लिए निष्पादन का एक ब्रेक डाउन है, BUILD_LIST लिए [] और CALL_FUNCTION लिए CALL_FUNCTION list()

BUILD_LIST निर्देश:

आपको सिर्फ डरावनी दिखानी चाहिए:

PyObject *list =  PyList_New(oparg);
if (list == NULL)
    goto error;
while (--oparg >= 0) {
    PyObject *item = POP();
    PyList_SET_ITEM(list, oparg, item);
}
PUSH(list);
DISPATCH();

बहुत गड़बड़, मुझे पता है। यह कितना आसान है:

  • PyList_New साथ एक नई सूची बनाएं (यह मुख्य रूप से एक नई सूची ऑब्जेक्ट के लिए स्मृति आवंटित करता है), स्टैक पर तर्कों की संख्या को oparg करने का विकल्प। सीधा मुद्दे पर।
  • जांचें कि if (list==NULL) कुछ भी गलत हो गया है if (list==NULL)
  • PyList_SET_ITEM (एक मैक्रो) के साथ स्टैक पर स्थित किसी भी तर्क (हमारे मामले में यह निष्पादित नहीं किया गया है) PyList_SET_ITEM

कोई आश्चर्य नहीं कि यह तेज़ है! यह नई सूचियां बनाने के लिए कस्टम बनाया गया है, और कुछ नहीं :-)

CALL_FUNCTION निर्देश:

जब आप कोड को संभालने वाले कोड पर देखते हैं तो पहली बात यह है कि CALL_FUNCTION :

PyObject **sp, *res;
sp = stack_pointer;
res = call_function(&sp, oparg, NULL);
stack_pointer = sp;
PUSH(res);
if (res == NULL) {
    goto error;
}
DISPATCH();

बहुत हानिकारक लग रहा है, है ना? खैर, नहीं, दुर्भाग्य से नहीं, call_function एक सीधा व्यक्ति नहीं है जो तुरंत फ़ंक्शन को कॉल करेगा, यह नहीं कर सकता है। इसके बजाए, यह वस्तु को ढेर से पकड़ लेता है, ढेर के सभी तर्कों को पकड़ता है और फिर वस्तु के प्रकार के आधार पर स्विच करता है; क्या यह:

  • PyCFunction_Type ? नहीं, यह list , list PyCFunction का प्रकार नहीं है
  • PyMethodType ? नहीं, पिछले देखें।
  • PyFunctionType ? नहीं, पिछले देखें।

हम list प्रकार को कॉल कर रहे हैं, call_function में पारित तर्क call_function है। सीपीथन को अब _PyObject_FastCallKeywords नामक किसी भी कॉल करने योग्य ऑब्जेक्ट को संभालने के लिए एक सामान्य फ़ंक्शन को कॉल करना होगा, और अधिक फ़ंक्शन कॉल।

यह फ़ंक्शन फिर से कुछ फ़ंक्शन प्रकारों (जिन्हें मैं समझ नहीं सकता) के लिए कुछ चेक करता है और फिर, यदि आवश्यक हो तो kwargs के लिए एक _PyObject_FastCallDict बनाने के बाद, _PyObject_FastCallDict को कॉल करने के लिए चला जाता है।

_PyObject_FastCallDict अंत में हमें कहीं और मिलता है! और भी अधिक जांच करने के बाद यह हमारे द्वारा पारित type के type से tp_call स्लॉट पकड़ लेता है, tp_call , यह type.tp_call . type.tp_call । इसके बाद यह _PyStack_AsTuple साथ पारित तर्कों में से एक _PyStack_AsTuple बनाने के लिए आगे _PyStack_AsTuple और अंततः, एक कॉल अंत में बनाया जा सकता है !

tp_call , जो type.__call__ मेल खाता है type.__call__ लेता है और आखिरकार सूची वस्तु बनाता है। यह सूचियों को __new__ कहता है जो __new__ से मेल PyType_GenericNew और इसके लिए PyType_GenericAlloc साथ स्मृति आवंटित करता है: यह वास्तव में वह हिस्सा है जहां यह PyList_New साथ PyList_New । एक सामान्य फैशन में वस्तुओं को संभालने के लिए सभी पिछले आवश्यक हैं।

अंत में, type_call कॉल list.__init__ और किसी भी उपलब्ध तर्क के साथ सूची प्रारंभ करता है, फिर हम वापस आने के तरीके पर वापस लौटते हैं। :-)

आखिरकार, LOAD_NAME को LOAD_NAME , यह एक और लड़का है जो यहां योगदान देता है।

यह देखना आसान है कि, हमारे इनपुट से निपटने पर, पाइथन को आम तौर पर नौकरी करने के लिए उपयुक्त C फ़ंक्शन को खोजने के लिए हुप्स के माध्यम से कूदना होता है। इसमें तुरंत कॉल करने की कड़वाहट नहीं है क्योंकि यह गतिशील है, कोई व्यक्ति मास्क list ( और लड़का कई लोगों को कर सकता है ) और दूसरा रास्ता लिया जाना चाहिए।

यह वह जगह है जहां list() बहुत अधिक खो जाती है: एक्सप्लोरिंग पायथन को यह पता लगाने की ज़रूरत है कि उसे क्या करना चाहिए।

दूसरी ओर, शाब्दिक वाक्यविन्यास, वास्तव में एक बात का मतलब है; इसे बदला नहीं जा सकता है और हमेशा पूर्व निर्धारित तरीके से व्यवहार करता है।

फुटनोट: सभी फ़ंक्शन नाम एक रिलीज से दूसरी रिलीज में बदल सकते हैं। बिंदु अभी भी खड़ा है और संभवतः किसी भी भविष्य के संस्करणों में खड़ा होगा, यह गतिशील लुकअप है जो चीजों को धीमा कर देता है।




Links