थोड़ी देर के लूप का उपयोग करने की तुलना में पायथन में लूपिंग ओवर रेंज () क्यों तेज है?


81

दूसरे दिन मैं कुछ पायथन बेंचमार्किंग कर रहा था और मुझे कुछ दिलचस्प लगा। नीचे दो छोरें हैं जो कमोबेश एक ही काम करती हैं। लूप 1 निष्पादित करने के लिए लूप 2 के रूप में लंबे समय तक लगभग दो बार लेता है।

लूप 1:

int i = 0
while i < 100000000:
  i += 1

लूप 2:

for n in range(0,100000000):
  pass

पहला लूप इतना धीमा क्यों है? मुझे पता है कि यह एक तुच्छ उदाहरण है लेकिन यह मेरी रुचि को बढ़ाता है। क्या रेंज () फ़ंक्शन के बारे में कुछ विशेष है जो एक चर को उसी तरह बढ़ाने की तुलना में अधिक कुशल बनाता है?

जवाबों:


159

अजगर बाइट कोड के disassembly देखें, आप एक अधिक ठोस विचार प्राप्त कर सकते हैं

लूप करते समय उपयोग करें:

1           0 LOAD_CONST               0 (0)
            3 STORE_NAME               0 (i)

2           6 SETUP_LOOP              28 (to 37)
      >>    9 LOAD_NAME                0 (i)              # <-
           12 LOAD_CONST               1 (100000000)      # <-
           15 COMPARE_OP               0 (<)              # <-
           18 JUMP_IF_FALSE           14 (to 35)          # <-
           21 POP_TOP                                     # <-

3          22 LOAD_NAME                0 (i)              # <-
           25 LOAD_CONST               2 (1)              # <-
           28 INPLACE_ADD                                 # <-
           29 STORE_NAME               0 (i)              # <-
           32 JUMP_ABSOLUTE            9                  # <-
      >>   35 POP_TOP
           36 POP_BLOCK

लूप बॉडी में 10 ऑप हैं

उपयोग सीमा:

1           0 SETUP_LOOP              23 (to 26)
            3 LOAD_NAME                0 (range)
            6 LOAD_CONST               0 (0)
            9 LOAD_CONST               1 (100000000)
           12 CALL_FUNCTION            2
           15 GET_ITER
      >>   16 FOR_ITER                 6 (to 25)        # <-
           19 STORE_NAME               1 (n)            # <-

2          22 JUMP_ABSOLUTE           16                # <-
      >>   25 POP_BLOCK
      >>   26 LOAD_CONST               2 (None)
           29 RETURN_VALUE

लूप बॉडी में 3 ऑप हैं

सी कोड को चलाने का समय घुसपैठिए की तुलना में बहुत कम है और इसे अनदेखा किया जा सकता है।


2
वास्तव में पहले डिस्सैड में लूप बॉडी में 10 ऑपरेशन होते हैं (स्थिति 32 से 9 तक की छलांग)। वर्तमान सीपीथॉन कार्यान्वयन में सीपीयू में महंगा अप्रत्यक्ष शाखा के दुरुपयोग (अगले बाइटकोड के कार्यान्वयन के लिए कूद) में काफी उच्च संभावना के साथ प्रत्येक बाइटकोड परिणामों की व्याख्या लागू होती है। यह सीपीथॉन के वर्तमान कार्यान्वयन का एक परिणाम है, जेआईएल को अनियंत्रित निगल, PyPy और अन्य द्वारा कार्यान्वित किया जा रहा है जो सबसे अधिक संभावना है कि ओवरहेड खो देंगे। उनमें से सर्वश्रेष्ठ भी परिमाण स्पीडअप के एक आदेश के लिए प्रकार विशेषज्ञता करने में सक्षम होंगे।
चींटियाँ प्लाज्मा

5
"जिले" मॉड्यूल का उपयोग करें। किसी फ़ंक्शन में अपने कोड को परिभाषित करें, फिर dis.disco (func .__ code__) पर कॉल करें
kcwu

क्या यह कहना सही होगा कि उच्च स्तर पर, एक whileलूप को प्रत्येक पुनरावृत्ति पर तुलना करना पड़ता है?
davidhood2

35

range()सी में लागू किया जाता है, जबकि i += 1व्याख्या की जाती है।

उपयोग xrange()करने से बड़ी संख्या के लिए यह और भी तेज हो सकता है। Python 3.0 range()से शुरू करना पहले जैसा ही है xrange()


15

यह कहना होगा कि लूप के साथ बहुत सी वस्तु निर्माण और विनाश हो रहा है।

i += 1

के समान है:

i = i + 1

लेकिन क्योंकि पायथन इनट्स अपरिवर्तनीय हैं, यह मौजूदा ऑब्जेक्ट को संशोधित नहीं करता है; बल्कि यह एक नए मूल्य के साथ एक नई वस्तु बनाता है। यह मूल रूप से है:

i = new int(i + 1)   # Using C++ or Java-ish syntax

कचरा उठाने वाले के पास बड़ी मात्रा में सफाई करने के लिए भी होगा। "वस्तु निर्माण महंगा है"।


4

क्योंकि आप व्याख्याकार में सी में लिखे गए कोड में अधिक बार चल रहे हैं। यानी i + = 1 पायथन में है, इतना धीमा (तुलनात्मक रूप से), जबकि रेंज (0, ...) एक सी है जिसे लूप के लिए कॉल किया जाएगा, ज्यादातर सी में भी निष्पादित होगा।


1

पायथन के अधिकांश बिल्ट इन मेथड कॉल्स को C कोड के रूप में चलाया जाता है। जिस कोड की व्याख्या की जानी है, वह बहुत धीमा है। स्मृति दक्षता और निष्पादन की गति के मामले में अंतर बहुत बड़ा है। अजगर इंटर्न को चरम पर अनुकूलित किया गया है, और यह उन अनुकूलन का लाभ उठाने के लिए सबसे अच्छा है।


0

मुझे लगता है कि अन्य उत्तरों की तुलना में यहां उत्तर थोड़ा अधिक सूक्ष्म है, हालांकि इसका सार सही है: लूप के लिए तेजी से है क्योंकि अधिक संचालन सी में होता है और पायथन में कम होता है

विशेष रूप से, लूप केस के लिए, C में दो चीजें होती हैं, जबकि लूप को पायथन में संभाला जाता है:

  1. जबकि लूप में, तुलना i < 100000000को पायथन में निष्पादित किया जाता है, जबकि लूप के लिए, जॉब को range(100000000)पुनरावृत्ति के लिए पारित किया जाता है , जो आंतरिक रूप से सी में पुनरावृत्ति (और इसलिए सीमा की जांच) करता है।

  2. लूप में लूप अपडेट i += 1, पायथन में होता है, जबकि लूप के लिए फिर से पुनरावृति का range(100000000)सी, सी में लिखा i+=1(या ++i) करता है।

हम देख सकते हैं कि यह इन दोनों चीजों का एक संयोजन है जो अंतर को देखने के लिए मैन्युअल रूप से जोड़कर लूप के लिए तेजी से बनाता है।

import timeit

N = 100000000


def while_loop():
    i = 0
    while i < N:
        i += 1


def for_loop_pure():
    for i in range(N):
        pass


def for_loop_with_increment():
    for i in range(N):
        i += 1


def for_loop_with_test():
    for i in range(N):
        if i < N: pass


def for_loop_with_increment_and_test():
    for i in range(N):
        if i < N: pass
        i += 1


def main():
    print('while loop\t\t', timeit.timeit(while_loop, number=1))
    print('for pure\t\t', timeit.timeit(for_loop_pure, number=1))
    print('for inc\t\t\t', timeit.timeit(for_loop_with_increment, number=1))
    print('for test\t\t', timeit.timeit(for_loop_with_test, number=1))
    print('for inc+test\t', timeit.timeit(for_loop_with_increment_and_test, number=1))


if __name__ == '__main__':
    main()

मैंने दोनों को 100000000 की संख्या के साथ शाब्दिक स्थिर बनाने की कोशिश की और इसके साथ एक चर के Nरूप में अधिक विशिष्ट होगा।

# inline constant N
while loop      3.5131139
for pure        1.3211338000000001
for inc         3.5477727000000003
for test        2.5209639
for inc+test    4.697028999999999

# variable N
while loop      4.1298240999999996
for pure        1.3526357999999998
for inc         3.6060175
for test        3.1093069
for inc+test    5.4753364

जैसा कि आप देख सकते हैं, दोनों मामलों में, whileसमय के अंतर के बहुत करीब है for inc+testऔर for pure। यह भी ध्यान दें कि जिस मामले में हम Nचर का उपयोग करते हैं, उसमें whileबार-बार मान को देखने के लिए एक अतिरिक्त मंदी है N, लेकिन forऐसा नहीं करता है।

यह वास्तव में पागल है कि इस तरह के तुच्छ संशोधनों से 3x कोड स्पीडअप हो सकता है , लेकिन यह आपके लिए पायथन है। और मुझे तब भी शुरू न करें जब आप एक बिलिन से अधिक बिलियन का उपयोग कर सकते हैं ...।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.