2012-08-15 16 views
23

मैं इस question का जवाब दे रहा था, मैं जनरेटर अभिव्यक्ति यहाँ प्राथमिकता दी है और इस है, जो मैंने सोचा था कि तेजी से जनरेटर पहले पूरी सूची बनाने के लिए की जरूरत नहीं है के रूप में किया जाएगा इस्तेमाल किया:जनरेटर अभिव्यक्ति के अजीब समय के परिणाम बनाम सूची समझ?

>>> lis=[['a','b','c'],['d','e','f']] 
>>> 'd' in (y for x in lis for y in x) 
True 

और लेवोन में सूची समझ का इस्तेमाल किया उसके

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f']]" "'d' in (y for x in lis for y in x)" 
    100000 loops, best of 3: 2.36 usec per loop 
~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f']]" "'d' in [y for x in lis for y in x]" 
    100000 loops, best of 3: 1.51 usec per loop 
: solution,

>>> lis = [['a','b','c'],['d','e','f']] 
>>> 'd' in [j for i in mylist for j in i] 
True 

लेकिन जब मैं इन नियंत्रण रेखा के लिए timeit परिणाम किया जनरेटर की तुलना में तेजी थी

तो मैं सूची के आकार में वृद्धि, और इसे फिर से समय समाप्त हो गया:

lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]] 

खोज 'd' जनरेटर के लिए इस बार तेजी से नियंत्रण रेखा से था, लेकिन जब मैं एक मध्यम तत्व (11) और पिछले तत्व तो नियंत्रण रेखा फिर से खोजा गया जनरेटर अभिव्यक्ति को धड़कता है, और मैं समझ नहीं पा रहा हूं क्यों?

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "'d' in (y for x in lis for y in x)" 
    100000 loops, best of 3: 2.96 usec per loop 

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "'d' in [y for x in lis for y in x]" 
    100000 loops, best of 3: 7.4 usec per loop 

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "11 in [y for x in lis for y in x]" 
100000 loops, best of 3: 5.61 usec per loop 

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "11 in (y for x in lis for y in x)" 
100000 loops, best of 3: 9.76 usec per loop 

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "18 in (y for x in lis for y in x)" 
100000 loops, best of 3: 8.94 usec per loop 

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "18 in [y for x in lis for y in x]" 
100000 loops, best of 3: 7.13 usec per loop 
+2

+1 मुझे जवाबों के लिए भी ट्यून किया जाएगा :) – Levon

+0

शायद कैशिंग के कारण ... और जनरेटर ... शायद अधिक कॉल की आवश्यकता है (अगली, उपज, राज्य को बचाने आदि)। और जनरेटर वास्तव में स्मृति कुशल हैं। वह पक्का है। – User007

+0

क्यों न केवल '(x में x के लिए x में x)'? –

उत्तर

23

Paulo के उत्तर पर विस्तार, जेनरेटर एक्सप्रेशन अक्सर फंक्शन कॉल के ओवरहेड की वजह से सूची समझ से धीमे होते हैं। इस मामले में, in ऑफसेट्स का संक्षिप्त सर्किटिंग व्यवहार जो आइटम को काफी जल्दी पाया जाता है, लेकिन अन्यथा, पैटर्न धारण करता है।

मैंने अधिक विस्तृत विश्लेषण के लिए प्रोफाइलर के माध्यम से एक साधारण लिपि चलायी। यहां स्क्रिप्ट है:

lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6], 
    [7,8,9],[10,11,12],[13,14,15],[16,17,18]] 

def ge_d(): 
    return 'd' in (y for x in lis for y in x) 
def lc_d(): 
    return 'd' in [y for x in lis for y in x] 

def ge_11(): 
    return 11 in (y for x in lis for y in x) 
def lc_11(): 
    return 11 in [y for x in lis for y in x] 

def ge_18(): 
    return 18 in (y for x in lis for y in x) 
def lc_18(): 
    return 18 in [y for x in lis for y in x] 

for i in xrange(100000): 
    ge_d() 
    lc_d() 
    ge_11() 
    lc_11() 
    ge_18() 
    lc_18() 

यहां प्रासंगिक परिणाम हैं, जो पैटर्न को स्पष्ट बनाने के लिए पुन: व्यवस्थित हैं।

  5400002 function calls in 2.830 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    100000 0.158 0.000 0.251 0.000 fop.py:3(ge_d) 
    500000 0.092 0.000 0.092 0.000 fop.py:4(<genexpr>) 
    100000 0.285 0.000 0.285 0.000 fop.py:5(lc_d) 

    100000 0.356 0.000 0.634 0.000 fop.py:8(ge_11) 
    1800000 0.278 0.000 0.278 0.000 fop.py:9(<genexpr>) 
    100000 0.333 0.000 0.333 0.000 fop.py:10(lc_11) 

    100000 0.435 0.000 0.806 0.000 fop.py:13(ge_18) 
    2500000 0.371 0.000 0.371 0.000 fop.py:14(<genexpr>) 
    100000 0.344 0.000 0.344 0.000 fop.py:15(lc_18) 

जनरेटर अभिव्यक्ति बनाना creating a generator function and calling it के बराबर है। यह एक कॉल के लिए <genexpr> पर कॉल करता है। फिर, पहले मामले में, next को 4 बार कहा जाता है, जब तक कुल 5 कॉल (समय 100000 पुनरावृत्तियों = ncalls = 500000) के लिए d तक पहुंच जाता है। दूसरे मामले में, कुल 18 कॉल के लिए इसे 17 बार कहा जाता है; और कुल मिलाकर, 25 बार कुल 25 कॉल के लिए।

जीनेक्स पहले मामले में सूची समझ को बेहतर बनाता है, लेकिन अतिरिक्त कॉल के लिए next अतिरिक्त कॉल सूची की गति की गति और जनरेटर अभिव्यक्ति की गति के बीच दूसरे अंतर और तीसरे मामलों में अधिक अंतर के लिए खाता है।

>>> .634 - .278 - .333 
0.023 
>>> .806 - .371 - .344 
0.091 

मुझे यकीन नहीं है कि शेष समय के लिए क्या खाते हैं; ऐसा लगता है कि जेनरेटर एक्सप्रेशन अतिरिक्त फंक्शन कॉल के बावजूद बालों को धीमा कर देगा। मुझे लगता है कि यह inspectorG4dget की पुष्टि करता है कि "जेनरेटर समझ बनाने के लिए सूची समझने की तुलना में अधिक देशी उपरि है।" लेकिन किसी भी मामले में, यह स्पष्ट रूप से दिखाता है कि जनरेटर अभिव्यक्ति अधिकnext पर कॉल के कारण धीमी है।

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

>>> counter = itertools.count() 
>>> lol = [[counter.next(), counter.next(), counter.next()] 
      for _ in range(1000000)] 
>>> 2999999 in (i for sublist in lol for i in sublist) 
True 
>>> 3000000 in (i for sublist in lol for i in sublist) 
False 
>>> %timeit 2999999 in [i for sublist in lol for i in sublist] 
1 loops, best of 3: 312 ms per loop 
>>> %timeit 2999999 in (i for sublist in lol for i in sublist) 
1 loops, best of 3: 351 ms per loop 
>>> %timeit any([2999999 in sublist for sublist in lol]) 
10 loops, best of 3: 161 ms per loop 
>>> %timeit any(2999999 in sublist for sublist in lol) 
10 loops, best of 3: 163 ms per loop 
>>> %timeit for i in [2999999 in sublist for sublist in lol]: pass 
1 loops, best of 3: 171 ms per loop 
>>> %timeit for i in (2999999 in sublist for sublist in lol): pass 
1 loops, best of 3: 183 ms per loop 

आप देख सकते हैं, जब कम सर्किटिंग अप्रासंगिक है, सूची comprehensions लगातार तेजी से भी सूचियों में से एक लाख मद-लंबी सूची के लिए हैं। स्पष्ट रूप से इन तराजू पर in के वास्तविक उपयोगों के लिए, जेनरेटर शॉर्ट सर्किटिंग के कारण तेज़ी से होंगे। लेकिन अन्य प्रकार के पुनरावृत्तियों के कार्यों के लिए जो वास्तव में वस्तुओं की संख्या में रैखिक हैं, सूची समझें हमेशा तेज हैं। यह विशेष रूप से सच है यदि आपको सूची में एकाधिक परीक्षण करने की आवश्यकता है; आप बहुत जल्दी एक पहले से ही निर्मित सूची समझ से अधिक पुनरावृति कर सकते हैं:

>>> incache = [2999999 in sublist for sublist in lol] 
>>> get_list = lambda: incache 
>>> get_gen = lambda: (2999999 in sublist for sublist in lol) 
>>> %timeit for i in get_list(): pass 
100 loops, best of 3: 18.6 ms per loop 
>>> %timeit for i in get_gen(): pass 
1 loops, best of 3: 187 ms per loop 

इस मामले में, सूची समझ परिमाण के एक आदेश तेजी से होता है!

बेशक, यह केवल तब तक सत्य रहता है जब तक कि आप स्मृति से बाहर नहीं हो जाते। जो मुझे मेरे अंतिम बिंदु पर लाता है। जेनरेटर का उपयोग करने के दो मुख्य कारण हैं: शॉर्ट सर्किटिंग का लाभ उठाने और स्मृति को बचाने के लिए। बहुत बड़े seqences/iterables के लिए, जेनरेटर जाने का स्पष्ट तरीका है, क्योंकि वे स्मृति को बचाते हैं। लेकिन यदि शॉर्ट-सर्किटिंग कोई विकल्प नहीं है, तो आप बहुत अधिक कभीगति के लिए सूचियों पर जेनरेटर चुनें।आपने उन्हें स्मृति को बचाने के लिए चुना है, और यह हमेशा एक व्यापार बंद है।

8

लोकप्रिय धारणा के विपरीत, सूची समझ मध्यम श्रेणियों के लिए बहुत अच्छी है। Iterator प्रोटोकॉल iterator.next() के लिए कॉल का तात्पर्य है, और पायथन में फ़ंक्शन कॉल महंगे हैं।

निश्चित रूप से जेनरेटर मेमोरी/सीपीयू ट्रेड-ऑफ का भुगतान करना शुरू हो जाएगा, लेकिन छोटी सेट सूची सूची के लिए समझ बहुत कुशल हैं।

+0

इसके अलावा, एक जेनरेटर समझ बनाने के लिए सूची की समझ – inspectorG4dget

12

पूरी तरह से डेटा पर निर्भर करता है।

जेनरेटर के पास एक निश्चित सेटअप समय है जिसे कितने आइटम कहा जाता है, पर अमूर्त होना चाहिए; सूची की समझ शुरुआत में तेज़ी से होती है लेकिन बड़े पैमाने पर धीमी हो जाएगी क्योंकि बड़े डेटा सेट के साथ अधिक मेमोरी का उपयोग किया जाता है।

याद रखें कि सीपीथन सूची का विस्तार किया गया है, इसलिए सूची 4, 8, 16, 25, 35, 46, 58, 72, 88,... के विकास पैटर्न में बदल दी गई है। बड़ी सूची की समझ के लिए, पायथन आपके डेटा के आकार की तुलना में 4x अधिक स्मृति आवंटित कर सकता है। एक बार जब आप वीएम हिट करते हैं --- वास्तव में sloowww! लेकिन, जैसा कि कहा गया है, सूची डेटा छोटे डेटा सेट के लिए जेनरेटर से तेज़ हैं।

इन समय में
LoL=[[c1,c2] for c1,c2 in zip(string.ascii_lowercase,string.ascii_uppercase)] 

def lc_d(item='d'): 
    return item in [i for sub in LoL for i in sub] 

def ge_d(item='d'): 
    return item in (y for x in LoL for y in x)  

def any_lc_d(item='d'): 
    return any(item in x for x in LoL)  

def any_gc_d(item='d'): 
    return any([item in x for x in LoL])  

def lc_z(item='z'): 
    return item in [i for sub in LoL for i in sub] 

def ge_z(item='z'): 
    return item in (y for x in LoL for y in x)  

def any_lc_z(item='z'): 
    return any(item in x for x in LoL)  

def any_gc_z(item='z'): 
    return any([item in x for x in LoL])    

cmpthese.cmpthese([lc_d,ge_d,any_gc_d,any_gc_z,any_lc_d,any_lc_z, lc_z, ge_z]) 

परिणाम::

मामले 1, सूचियों का एक 2x26 सूची पर विचार करें

  rate/sec ge_z lc_z lc_d any_lc_z any_gc_z any_gc_d ge_d any_lc_d 
ge_z  124,652  -- -10.1% -16.6% -44.3% -46.5% -48.5% -76.9% -80.7% 
lc_z  138,678 11.3%  -- -7.2% -38.0% -40.4% -42.7% -74.3% -78.6% 
lc_d  149,407 19.9% 7.7%  -- -33.3% -35.8% -38.2% -72.3% -76.9% 
any_lc_z 223,845 79.6% 61.4% 49.8%  -- -3.9% -7.5% -58.5% -65.4% 
any_gc_z 232,847 86.8% 67.9% 55.8%  4.0%  -- -3.7% -56.9% -64.0% 
any_gc_d 241,890 94.1% 74.4% 61.9%  8.1%  3.9%  -- -55.2% -62.6% 
ge_d  539,654 332.9% 289.1% 261.2% 141.1% 131.8% 123.1%  -- -16.6% 
any_lc_d 647,089 419.1% 366.6% 333.1% 189.1% 177.9% 167.5% 19.9%  -- 

अब मामले 2, कि बीच व्यापक असमानता दिखाने पर विचार एक एलसी और जीन।

इस समय में
LoL=[[str(a),str(b),str(c)] 
     for a in range(100) for b in range(97) for c in range(97)] 

def lc_10(item='10'): 
    return item in [i for sub in LoL for i in sub] 

def ge_10(item='10'): 
    return item in (y for x in LoL for y in x)  

def any_lc_10(item='10'): 
    return any([item in x for x in LoL])  

def any_gc_10(item='10'): 
    return any(item in x for x in LoL)  

def lc_99(item='99'): 
    return item in [i for sub in LoL for i in sub] 

def ge_99(item='99'): 
    return item in (y for x in LoL for y in x)  

def any_lc_99(item='99'): 
    return any(item in x for x in LoL)  

def any_gc_99(item='99'): 
    return any([item in x for x in LoL])  

cmpthese.cmpthese([lc_10,ge_10,any_lc_10,any_gc_10,lc_99,ge_99,any_lc_99,any_gc_99],c=10,micro=True) 

परिणाम:: इस मामले में, हम एक 100 x 97 x 97 संरचना की सूची तरह की सूची में एक तत्व की तलाश कर रहे

  rate/sec usec/pass  ge_99  lc_99  lc_10 any_lc_99 any_gc_99 any_lc_10 ge_10 any_gc_10 
ge_99   3 354545.903   --  -20.6%  -30.6%  -60.8%  -61.7%  -63.5% -100.0% -100.0% 
lc_99   4 281678.295  25.9%   --  -12.6%  -50.6%  -51.8%  -54.1% -100.0% -100.0% 
lc_10   4 246073.484  44.1%  14.5%   --  -43.5%  -44.8%  -47.4% -100.0% -100.0% 
any_lc_99  7 139067.292  154.9%  102.5%  76.9%   --  -2.4%  -7.0% -100.0% -100.0% 
any_gc_99  7 135748.100  161.2%  107.5%  81.3%  2.4%   --  -4.7% -100.0% -100.0% 
any_lc_10  8 129331.803  174.1%  117.8%  90.3%  7.5%  5.0%   -- -100.0% -100.0% 
ge_10  175,494  5.698 6221964.0% 4943182.0% 4318339.3% 2440446.0% 2382196.2% 2269594.1%  -- -38.5% 
any_gc_10 285,327  3.505 10116044.9% 8036936.7% 7021036.1% 3967862.6% 3873157.1% 3690083.0% 62.6%  -- 

आप देख सकते हैं - यह निर्भर करता है और यह एक व्यापार है ...

+1

जनरेटर भी पूरी सूची समाप्त नहीं करती है, इसकी तुलना में अधिक देशी ओवरहेड है, फिर भी यह धीमा है। –

+2

@ अश्विनी चौधरी: जेनरेटर डेटा की थोड़ी मात्रा के साथ एलसी की तुलना में केवल धीमा है। मामले 2 में तुलनात्मक गति को देखें और जनरेटर का उपयोग करके जनरेटर या 'कोई भी' निर्माण एलसी के बट को मारता है। जनरेटर तेज़ होते हैं, लेकिन उन्हें लंबे सेटअप समय को दूर करना होगा। –

+0

@ सेंडरले: बहुत अच्छे अंक, और मैंने उन्हें अपनी पोस्ट में संपादन में संबोधित किया। –

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