2015-06-07 8 views
8

पायथन 2.7.3 में एक स्तरीय वर्ग सदस्य में जोड़ने पर मुझे एक अजीब प्रदर्शन समस्या का सामना करना पड़ा है। मुझे पता है कि स्थानीय चर का उपयोग तेजी से होता है, हालांकि, नीचे दी गई समस्या में, दो लूपों के बीच 100x से अधिक गति अंतर होता है। जो a.accum_ का उपयोग करता है वह तेज़ी से शुरू होता है लेकिन धीमा हो जाता है, जैसे कि str iadd ओ (एन^2) स्ट्र की लंबाई के साथ है।पायथन सदस्य str प्रदर्शन बहुत धीमा

क्या किसी को कारण पता है?

# Fast (< 1sec): 
accum = str() 
for ii in range(1000000): 
    if (ii % 10000) == 0: 
     print 'fast cnt', ii 
    accum += 'zzzzz\n' 

# Much slower (> 5 mins): 
class Foo: 
    pass 
a = Foo() 
a.accum_ = str() 
for ii in range(1000000): 
    if (ii % 10000) == 0: 
     print 'slow cnt', ii 
    a.accum_ += 'zzzzz\n' 
+0

यह निश्चित रूप से है क्योंकि पहले दृष्टिकोण में पहली बार स्ट्रिंग के जीवनकाल के दौरान केवल एक संदर्भ होगा और सीपीथॉन इस मामले को अनुकूलित कर सकता है, इसलिए ओ (एन) समय। दूसरे मामले में संदर्भ संख्या निश्चित रूप से 1 से बढ़ी है (लेकिन वास्तव में कहां?), इसलिए वर्गबद्ध समय। –

+0

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

+0

@doublep मुझे यकीन नहीं है कि आप संदर्भों की गणना करने की कोशिश कैसे कर रहे हैं, लेकिन '+ =' और ** के दौरान '+ =' और ** से पहले refcount की जांच करें। इसे कम से कम 1. –

उत्तर

3

अजगर तार अपरिवर्तनीय हैं और इसलिए एक __iadd__ विधि नहीं कर सकते हैं। पहले उदाहरण में आप जो देख रहे हैं वह सीपीथन दुभाषिया का एक माइक्रो ऑप्टिमाइज़ेशन है। पहले उदाहरण में दुभाषिया ने देखा है कि इसमें एक स्थानीय चर है जिसमें 1 का संदर्भ गिनती है। इस प्रकार, दुभाषिया आसानी से स्ट्रिंग को संशोधित करने के साथ दूर हो सकता है। भले ही यह str के अनुबंध का उल्लंघन करता है, कार्यक्रम के निष्पादन के दौरान किसी भी समय यह स्पष्ट नहीं होगा कि इस अनुबंध का संक्षेप में उल्लंघन किया गया था।

बाद के उदाहरण में यह माइक्रो ऑप्टिमाइज़ेशन लागू नहीं किया जा रहा है, यही कारण है कि यह इतना धीमा है। ऐसा लगता है कि अनुकूलन लागू किया जा सकता है, इसलिए मुझे यकीन नहीं है कि यह क्यों लागू नहीं किया जा रहा है।

आम तौर पर, यदि एक स्ट्रिंग का निर्माण होता है, तो सूची में सबस्ट्रिंग को एकत्रित करें और फिर अंतिम उत्पाद बनाने के लिए str.join का उपयोग करें।

+1

में बढ़ाना चाहिए प्रासंगिक अनुकूलन 'string_concatenate()' फ़ाइल 'ceval.c' में है। जाहिर है, यह सीपीथन के लिए प्रासंगिक है। – doublep

5

पहला उदाहरण यह बहुत स्पष्ट है कि यह भी संदर्भ अनुकूलन के मामले है के लिए (वहाँ दो संदर्भों वास्तव में कर रहे हैं: वस्तु से ही एक और एक LOAD_FAST; unicode_concatenatePyUnicode_Append करने के लिए नियंत्रण पार करने से पहले 1 करने के लिए इसे कम करने की कोशिश करेंगे) CPython द्वारा किया इस unicode_modifiable फ़ंक्शन का उपयोग:

static int 
unicode_modifiable(PyObject *unicode) 
{ 
    assert(_PyUnicode_CHECK(unicode)); 
    if (Py_REFCNT(unicode) != 1) 
     return 0; 
    if (_PyUnicode_HASH(unicode) != -1) 
     return 0; 
    if (PyUnicode_CHECK_INTERNED(unicode)) 
     return 0; 
    if (!PyUnicode_CheckExact(unicode)) 
     return 0; 
#ifdef Py_DEBUG 
    /* singleton refcount is greater than 1 */ 
    assert(!unicode_is_singleton(unicode)); 
#endif 
    return 1; 
} 

लेकिन उदाहरण डेटा के रूप में दूसरे मामले में एक अजगर dict के बजाय एक सरल चर में संग्रहीत किया जाता है, तो चीजें थोड़ा अलग मिलता है।

a.accum_ += 'foo' 

वास्तव में a.accum_ का मूल्य प्रीफ़ेचिंग और ढेर के लिए उस पर भंडारण की आवश्यकता है। तो, अब स्ट्रिंग कम से कम तीन संदर्भ: उदाहरण शब्दकोश से एक, DUP_TOP से एक और PyObject_GetAttr से LOAD_ATTR द्वारा उपयोग किया गया है। इसलिए, पाइथन इस मामले को अनुकूलित नहीं कर सकता क्योंकि उनमें से एक को संशोधित करने से अन्य संदर्भ भी प्रभावित होंगे।

>>> class A: 
    pass 
... 
>>> a = A() 
>>> def func(): 
    a.str = 'spam' 
    print a.str 
    return '_from_func' 
... 
>>> a.str = 'foo' 
>>> a.str += func() 
spam 

आप उत्पादन यहाँ 'spam_from_func' होने की अपेक्षा करेंगे, लेकिन यह क्योंकि a.str के मूल मूल्य अजगर द्वारा जमा हो गया था इससे पहले कि func() बुलाया गया था अलग होने जा रहा है।

>>> a.str 
'foo_from_func' 

बाइट कोड:

>>> import dis 
>>> def func_class(): 
     a = Foo() 
     a.accum = '' 
     a.accum += 'zzzzz\n' 
... 
>>> dis.dis(func_class) 
    2   0 LOAD_GLOBAL    0 (Foo) 
       3 CALL_FUNCTION   0 (0 positional, 0 keyword pair) 
       6 STORE_FAST    0 (a) 

    3   9 LOAD_CONST    1 ('') 
      12 LOAD_FAST    0 (a) 
      15 STORE_ATTR    1 (accum) 

    4   18 LOAD_FAST    0 (a) 
      21 DUP_TOP 
      22 LOAD_ATTR    1 (accum) 
      25 LOAD_CONST    2 ('zzzzz\n') 
      28 INPLACE_ADD 
      29 ROT_TWO 
      30 STORE_ATTR    1 (accum) 
      33 LOAD_CONST    0 (None) 
      36 RETURN_VALUE 

ध्यान दें कि यह अनुकूलन around 2004 में किया गया था (CPython 2।4) उपयोगकर्ताओं को a += b या a = a + b की धीमी गति से रोकने के लिए, इसलिए यह ज्यादातर सरल चर के लिए होता है और केवल तभी काम करता है जब अगला निर्देश STORE_FAST (स्थानीय चर), STORE_DEREF (बंद) और STORE_NAME है। यह एक सामान्य समाधान नहीं है, the best way to do this in Python is to create a list and join its items using str.join

CPython कार्यान्वयन विस्तार: s और t दोनों तार कर रहे हैं, इस तरह के CPython के रूप में कुछ अजगर कार्यान्वयन आमतौर पर प्रपत्र s = s + t या s += t के कार्य के लिए एक में जगह अनुकूलन कर सकते हैं। जब लागू होता है, तो यह अनुकूलन वर्गबद्ध रन-टाइम को कम संभव बनाता है। यह अनुकूलन दोनों संस्करण और कार्यान्वयन निर्भर है। प्रदर्शन संवेदनशील कोड के लिए, str.join() विधि का उपयोग करना बेहतर है जो लगातार रैखिक concatenation संस्करणों और कार्यान्वयन में प्रदर्शन का आश्वासन देता है।

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