2011-11-25 16 views
151

यह an answer I gave a few days back पर एक फॉलो-अप प्रश्न है। संपादित करें: ऐसा लगता है कि उस प्रश्न के ओपी पहले से ही the same question पूछने के लिए कोड मैं उसे करने के लिए तैनात थे, लेकिन मैं इसके बारे में अनजान था। क्षमा याचना। हालांकि दिए गए उत्तर अलग हैं!जल्दी से वापसी धीमी क्यों है?

काफी हद तक मैंने देखा कि:

>>> 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 हालत या ट्रिगर नहीं किया जा रहा है तेजी से परवाह किए बिना।

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

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

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

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

+3

3.x पर, दोनों unreachable कोड ('LOAD_CONST (कोई नहीं); RETURN_VALUE' के लिए ** समान ** बाइटकोड सहेजते हैं - लेकिन जैसा कि कहा गया है, यह कभी नहीं पहुंचा है)' with_else' के अंत में। मुझे अत्यधिक संदेह है कि मृत कोड एक समारोह को तेज बनाता है। क्या कोई 2.7 पर 'डी' प्रदान कर सकता है? – delnan

+4

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

उत्तर

329

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

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

यहाँ अजगर 2.7 में disassembly के परिणाम हैं: python निम्नलिखित संस्करण जानकारी उत्पन्न करता है चाहे वह सही है, लेकिन मेरे पास आपके लिए एक सिद्धांत है।

मैं अपने कोड की कोशिश की और प्राप्त परिणामों के एक ही, 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_elseglobals() में कुछ और के साथ एक हैश टकराव है कि इतने वैश्विक नाम देखने थोड़ी धीमी है।

संपादित: 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__ हैश टू -1196389688 जो तालिका आकार (32) मॉड्यूल को कम करता है इसका मतलब है कि यह तालिका के # 8 स्लॉट में संग्रहीत है। 505688136 के लिए

without_else हैश जो सापेक्ष 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 के मामले में होता है) भरा हुआ है लेकिन तालिका में संग्रहीत पूर्ण हैश मान कुंजी हम देख रहे के हैश के रूप में ही नहीं है।

  • स्लॉट भरा हुआ है और वास्तव में हैश मान हम चाहते हैं, तो अजगर चेकों को देखने के लिए है, तो कुंजी और वस्तु हम देख रहे हैं एक ही वस्तु हैं (इस मामले में वे क्योंकि छोटी स्ट्रिंग हो जाएगा जो जो पहचानकर्ताओं को प्रशिक्षित किया जा सकता है, समान पहचानकर्ता सटीक समान स्ट्रिंग का उपयोग करते हैं)।

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

+50

शानदार जासूस काम, अच्छा जवाब –

+0

अकेले नाम की लंबाई एक कारक हो सकती है, क्योंकि हैश कोड फ़ंक्शन स्केल रैखिक रूप से। –

+9

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

संबंधित मुद्दे