अन्य की तुलना में जल्दी वापसी धीमी क्यों है?


180

यह एक अनुवर्ती प्रश्न है जिसका उत्तर मैंने कुछ दिन पहले दिया थासंपादित करें: ऐसा लगता है कि उस प्रश्न के ओपी ने पहले से ही उसी प्रश्न को पूछने के लिए मेरे द्वारा पोस्ट किए गए कोड का उपयोग किया था, लेकिन मैं इससे अनजान था। क्षमा याचना। प्रदान किए गए उत्तर हालांकि अलग हैं!

मुख्य रूप से मैंने देखा कि:

>>> def without_else(param=False):
...     if param:
...         return 1
...     return 0
>>> def with_else(param=False):
...     if param:
...         return 1
...     else:
...         return 0
>>> from timeit import Timer as T
>>> T(lambda : without_else()).repeat()
[0.3011460304260254, 0.2866089344024658, 0.2871549129486084]
>>> T(lambda : with_else()).repeat()
[0.27536892890930176, 0.2693932056427002, 0.27011704444885254]
>>> T(lambda : without_else(True)).repeat()
[0.3383951187133789, 0.32756996154785156, 0.3279120922088623]
>>> T(lambda : with_else(True)).repeat()
[0.3305950164794922, 0.32186388969421387, 0.3209099769592285]

... या दूसरे शब्दों में: elseक्लॉज की ifस्थिति तेज होने या न होने के बावजूद तेज होना।

मुझे लगता है कि यह दोनों द्वारा उत्पन्न अलग-अलग बायोटेक के साथ करना है, लेकिन क्या कोई विस्तार से पुष्टि / व्याख्या करने में सक्षम है?

संपादित करें: ऐसा लगता है कि हर कोई मेरी टाइमिंग को पुन: पेश करने में सक्षम नहीं है, इसलिए मैंने सोचा कि यह मेरे सिस्टम पर कुछ जानकारी देने के लिए उपयोगी हो सकता है। मैं Ubuntu 11.10 64 बिट डिफ़ॉल्ट अजगर स्थापित के साथ चला रहा हूं। pythonनिम्न संस्करण जानकारी उत्पन्न करता है:

Python 2.7.2+ (default, Oct  4 2011, 20:06:09) 
[GCC 4.6.1] on linux2

यहाँ अजगर 2.7 में disassembly के परिणाम हैं:

>>> dis.dis(without_else)
  2           0 LOAD_FAST                0 (param)
              3 POP_JUMP_IF_FALSE       10

  3           6 LOAD_CONST               1 (1)
              9 RETURN_VALUE        

  4     >>   10 LOAD_CONST               2 (0)
             13 RETURN_VALUE        
>>> dis.dis(with_else)
  2           0 LOAD_FAST                0 (param)
              3 POP_JUMP_IF_FALSE       10

  3           6 LOAD_CONST               1 (1)
              9 RETURN_VALUE        

  5     >>   10 LOAD_CONST               2 (0)
             13 RETURN_VALUE        
             14 LOAD_CONST               0 (None)
             17 RETURN_VALUE        

1
एसओ पर एक समान प्रश्न था जो मुझे अब नहीं मिल सकता है। उन्होंने जेनरेट किया गया बायटेकोड चेक किया और एक अतिरिक्त कदम था। देखे गए अंतर परीक्षक (मशीन, एसओ ..) पर बहुत निर्भर थे, कभी-कभी केवल बहुत छोटे अंतर पाते थे।
जौक्विन

3
3.x पर, दोनों कुछ अगम्य कोड के लिए समान बाइटकोड सेव का उत्पादन करते हैं ( LOAD_CONST(None); RETURN_VALUE- लेकिन जैसा कि कहा गया है, यह कभी नहीं पहुंचता है) के अंत में with_else। मुझे अत्यधिक संदेह है कि मृत कोड तेजी से कार्य करता है। किसी dis2.7 पर प्रदान कर सकता है ?

4
मैं इसे पुन: पेश करने में सक्षम नहीं था। उन सभी (152ns) के साथ कार्य elseऔर Falseसबसे धीमा। दूसरा सबसे तेज Trueबिना else(143ns) था और दो अन्य मूल रूप से समान (137ns और 138ns) थे। मैंने डिफ़ॉल्ट पैरामीटर का उपयोग नहीं किया और इसे %timeitiPython में मापा।
rplnt

मैं उन समयों को पुन: उत्पन्न नहीं कर सकता, कभी-कभी with_else तेज होता है, कभी-कभी यह without_else संस्करण होता है, ऐसा लगता है कि वे मेरे लिए बहुत समान हैं ...
सेड्रिक जूलियन

1
Disassembly के परिणाम जोड़े गए। मैं Ubuntu 11.10, 64-बिट, स्टॉक पायथन 2.7 का उपयोग कर रहा हूं - @mac के समान कॉन्फ़िगरेशन। मैं यह भी कहता हूं कि with_elseयह बहुत तेजी से बढ़ रहा है।
क्रिस मॉर्गन

जवाबों:


387

यह एक शुद्ध अनुमान है, और मैंने यह जांचने का एक आसान तरीका नहीं निकाला है कि क्या यह सही है, लेकिन मेरे पास आपके लिए एक सिद्धांत है।

मैंने आपके कोड की कोशिश की और परिणामों के समान है, without_else()बार-बार थोड़ा धीमा है with_else():

>>> T(lambda : without_else()).repeat()
[0.42015745017874906, 0.3188967452567226, 0.31984281521812363]
>>> T(lambda : with_else()).repeat()
[0.36009842032996175, 0.28962249392031936, 0.2927151355828528]
>>> T(lambda : without_else(True)).repeat()
[0.31709728471076915, 0.3172671387005721, 0.3285821242644147]
>>> T(lambda : with_else(True)).repeat()
[0.30939889008243426, 0.3035132258429485, 0.3046679117038593]

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

>>> def no_else(param=False):
    if param:
        return 1
    return 0

>>> T(lambda : no_else()).repeat()
[0.3359846013948413, 0.29025818923918223, 0.2921801513879245]
>>> T(lambda : no_else(True)).repeat()
[0.3810395594970828, 0.2969634408842694, 0.2960104566362247]

मेरा अनुमान है कि without_elseकिसी अन्य चीज़ के साथ हैश की टक्कर है, globals()इसलिए वैश्विक नाम लुकअप थोड़ा धीमा है।

संपादित करें : 7 या 8 कुंजी वाले एक शब्दकोश में संभवतः 32 स्लॉट हैं, इसलिए उस आधार पर without_elseइसके साथ हैश टकराव होता है __builtins__:

>>> [(k, hash(k) % 32) for k in globals().keys() ]
[('__builtins__', 8), ('with_else', 9), ('__package__', 15), ('without_else', 8), ('T', 21), ('__name__', 25), ('no_else', 28), ('__doc__', 29)]

यह स्पष्ट करने के लिए कि हैशिंग कैसे काम करता है:

__builtins__ hashes to -1196389688 जिसने मोड्यूलो को टेबल के आकार को कम किया (32) इसका मतलब है कि यह टेबल के # 8 स्लॉट में संग्रहीत है।

without_else505688136 हैश को कम किया है जो मोडुलो 32 8 है इसलिए टक्कर है। इस अजगर को हल करने के लिए गणना करता है:

के साथ शुरू:

j = hash % 32
perturb = hash

इसे तब तक दोहराएं जब तक हमें एक मुफ्त स्लॉट नहीं मिल जाता:

j = (5*j) + 1 + perturb;
perturb >>= 5;
use j % 2**i as the next table index;

जो इसे अगले सूचकांक के रूप में उपयोग करने के लिए 17 देता है। सौभाग्य से यह मुफ़्त है इसलिए लूप केवल एक बार दोहराता है। हैश तालिका आकार 2 के एक शक्ति है, इसलिए 2**i, हैश तालिका का आकार है iहैश मान से बिट की संख्या है j

तालिका की प्रत्येक जांच इनमें से किसी एक को पा सकती है:

  • स्लॉट खाली है, उस स्थिति में जांच बंद हो जाती है और हमें पता है कि मूल्य तालिका में नहीं है।

  • स्लॉट अप्रयुक्त है लेकिन अतीत में इस्तेमाल किया गया था जिस स्थिति में हम ऊपर के रूप में गणना किए गए अगले मूल्य का प्रयास करते हैं।

  • स्लॉट भरा हुआ है लेकिन तालिका में संग्रहीत पूर्ण हैश मान उस कुंजी के हैश के समान नहीं है जिसकी हम तलाश कर रहे हैं ( __builtins__बनाम के मामले में ऐसा ही होता है without_else)।

  • स्लॉट भरा हुआ है और ठीक वैसा ही हैश वैल्यू है जो हम चाहते हैं, फिर पायथन यह देखने के लिए जाँच करता है कि क्या हम जिस कुंजी और ऑब्जेक्ट को देख रहे हैं, वही ऑब्जेक्ट हैं (जो इस मामले में वे होंगे क्योंकि शॉर्ट स्ट्रिंग्स जो कि पहचानकर्ता हो सकते हैं, उन्हें इंटर्न किया गया है समान पहचानकर्ता सटीक उसी स्ट्रिंग का उपयोग करते हैं)।

  • अंत में जब स्लॉट भरा है, हैश बिल्कुल मेल खाता है, लेकिन चाबियाँ समान वस्तु नहीं हैं, तो और उसके बाद ही पायथन समानता के लिए उनकी तुलना करने की कोशिश करेगा। यह तुलनात्मक रूप से धीमा है, लेकिन नाम देखने के मामले में वास्तव में ऐसा नहीं होना चाहिए।


9
@ क्रिस, स्ट्रिंग की लंबाई महत्वपूर्ण नहीं होनी चाहिए। पहली बार जब आप एक स्ट्रिंग हैश है यह स्ट्रिंग की लंबाई के लिए आनुपातिक समय लगेगा लेकिन तब परिकलित हैश स्ट्रिंग वस्तु में कैश्ड है इसलिए बाद में हैश ओ (1) हैं।
डंकन

1
आह ठीक है, मैं कैशिंग के बारे में नहीं जानता था, लेकिन इससे समझ में आता है
क्रिस एबर्ले

9
चित्त आकर्षण करनेवाला! क्या मैं आपको शर्लक कहूँ? ;) वैसे भी मुझे आशा है कि जैसे ही प्रश्न योग्य होगा मैं आपको इनाम के साथ कुछ अतिरिक्त अंक देना नहीं भूलूंगा।
वू

4
@ एमएसी, काफी नहीं। मैं हैश रिज़ॉल्यूशन के बारे में थोड़ा जोड़ूंगा (टिप्पणी में इसे निचोड़ने जा रहा था लेकिन यह मेरे विचार से अधिक दिलचस्प है)।
डंकन

6
@ डंकन - हैश प्रक्रिया का वर्णन करने के लिए समय निकालने के लिए आपका बहुत-बहुत धन्यवाद। शीर्ष पायदान जवाब! :)
मैक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.