[0: 0] = [0] की तुलना में बहुत अधिक धीमी (0,0) क्यों है?


61

सूची के insertफ़ंक्शन का उपयोग करना स्लाइस असाइनमेंट का उपयोग करके समान प्रभाव प्राप्त करने की तुलना में बहुत धीमा है:

> python -m timeit -n 100000 -s "a=[]" "a.insert(0,0)"
100000 loops, best of 5: 19.2 usec per loop

> python -m timeit -n 100000 -s "a=[]" "a[0:0]=[0]"
100000 loops, best of 5: 6.78 usec per loop

(ध्यान दें कि a=[]केवल सेटअप है, इसलिए aखाली शुरू होता है लेकिन फिर 100,000 तत्वों तक बढ़ता है।)

पहले मैंने सोचा कि शायद यह विशेषता लुकअप या फ़ंक्शन कॉल ओवरहेड या ऐसा है, लेकिन अंत के पास सम्मिलित करना दिखाता है कि यह नगण्य है:

> python -m timeit -n 100000 -s "a=[]" "a.insert(-1,0)"
100000 loops, best of 5: 79.1 nsec per loop

क्यों संभवतया सरल "समर्पित एकल तत्व" फ़ंक्शन इतना धीमा है?

मैं इसे repl.it पर भी पुन : पेश कर सकता हूं :

from timeit import repeat

for _ in range(3):
  for stmt in 'a.insert(0,0)', 'a[0:0]=[0]', 'a.insert(-1,0)':
    t = min(repeat(stmt, 'a=[]', number=10**5))
    print('%.6f' % t, stmt)
  print()

# Example output:
#
# 4.803514 a.insert(0,0)
# 1.807832 a[0:0]=[0]
# 0.012533 a.insert(-1,0)
#
# 4.967313 a.insert(0,0)
# 1.821665 a[0:0]=[0]
# 0.012738 a.insert(-1,0)
#
# 5.694100 a.insert(0,0)
# 1.899940 a[0:0]=[0]
# 0.012664 a.insert(-1,0)

मैं विंडोज 10 64-बिट पर पायथन 3.8.1 32-बिट का उपयोग करता हूं।
repl.it लिनक्स 64-बिट पर पायथन 3.8.1 64-बिट का उपयोग करता है।


ध्यान दें कि दिलचस्प a=[]; a[0:0]=[0]के रूप में ही होता हैa=[]; a[100:200]=[0]
smac89

क्या कोई कारण है कि आप इसे केवल एक खाली सूची के साथ परीक्षण कर रहे हैं?
MisterMiyagi

@MisterMiyagi खैर, मुझे कुछ के साथ शुरू करना होगा । ध्यान दें कि यह पहली प्रविष्टि के पहले ही खाली है और बेंचमार्क के दौरान 100,000 तत्वों तक बढ़ता है।
हीप ओवरफ्लो

@ smac89 दिलचस्प सूची के अंत में संलग्न a=[1,2,3];a[100:200]=[4]है । 4a
Ch3steR

1
@ smac89 जबकि यह सच है, इसका वास्तव में सवाल से कोई लेना-देना नहीं है और मुझे डर है कि यह किसी को यह सोचने में गुमराह कर सकता है कि मैं बेंचमार्किंग a=[]; a[0:0]=[0]कर रहा हूं या वह a[0:0]=[0]भी ऐसा ही करता है a[100:200]=[0]...
हीप ओवरफ्लो

जवाबों:


57

मुझे लगता है कि ऐसा शायद इसलिए है सिर्फ इतना है कि वे का उपयोग करना भूल गया memmoveमें list.insert। यदि आप तत्वों को स्थानांतरित करने के लिए उपयोग किए गए कोड पर एक नज़र डालते list.insertहैं, तो आप देख सकते हैं कि यह सिर्फ एक मैनुअल लूप है:

for (i = n; --i >= where; )
    items[i+1] = items[i];

जबकि list.__setitem__टुकड़ा काम रास्ते पर उपयोग करता हैmemmove :

memmove(&item[ihigh+d], &item[ihigh],
    (k - ihigh)*sizeof(PyObject *));

memmove आमतौर पर इसमें बहुत सारे अनुकूलन होते हैं, जैसे कि SSE / AVX निर्देशों का लाभ उठाना।


5
धन्यवाद। इसे संदर्भित करते हुए एक मुद्दा बनाया ।
हेल ​​ओवरफ्लो

7
यदि इंटरप्रिटर को -O3ऑटो-वैरिफिकेशन सक्षम किया गया है, तो यह मैनुअल लूप कुशलतापूर्वक संकलित कर सकता है। लेकिन जब तक कंपाइलर लूप को एक मेमोव के रूप में नहीं पहचानता है और इसे वास्तविक कॉल में संकलित करता है memmove, यह केवल संकलन-समय पर सक्षम इंस्ट्रक्शन-सेट एक्सटेंशन का लाभ उठा सकता है। (ठीक है अगर आप अपना खुद का निर्माण कर रहे हैं -march=native, तो बेसलाइन के साथ निर्मित दूरबीन के लिए इतना नहीं है)। और जीसीसी डिफ़ॉल्ट रूप से छोरों को तब तक अनियंत्रित नहीं करेगा जब तक आप पीजीओ ( -fprofile-generate/ रन / ...-use)
पीटर कॉर्ड्स

@PeterCordes क्या मैं आपको सही ढंग से समझता हूं कि यदि कंपाइलर इसे वास्तविक memmoveकॉल में संकलित करता है , तो क्या निष्पादन समय पर मौजूद सभी एक्सटेंशन का लाभ उठा सकते हैं?
हीप ओवरफ्लो

1
@ हेलोवरफ़्लो: हाँ। उदाहरण के लिए GNU / Linux, glibc एक फ़ंक्शन के साथ डायनेमिक लिंकर प्रतीक रिज़ॉल्यूशन को ओवरलोड करता है जो सहेजे गए सीपीयू-डिटेक्शन परिणामों के आधार पर इस मशीन के लिए मेमोव का सबसे अच्छा हाथ से लिखा एएसएम संस्करण चुनता है। (उदाहरण के लिए x86 पर, एक ग्लोब इनिट फ़ंक्शन का उपयोग करता है cpuid)। कई अन्य मेम / स्ट्रिंग फ़ंक्शंस के लिए भी। तो -O2डिस्ट्रोस रन-रन-बायनेरिज़ बनाने के लिए संकलन कर सकते हैं , लेकिन कम से कम मेकपी / मेमोव एक अनियंत्रित एवीएक्स लूप लोडिंग / 32 बाइट्स प्रति निर्देश का उपयोग करते हैं। (या यहां तक ​​कि कुछ CPUs पर AVX512 जहां यह एक अच्छा विचार है; मुझे लगता है कि सिर्फ Xeon Phi है।)
पीटर कॉर्डेस

1
@HeapOflowflow: नहीं, कई memmoveसंस्करणों में वहाँ बैठे हैं libराबो, साझा पुस्तकालय। प्रत्येक फ़ंक्शन के लिए, प्रतीक रिज़ॉल्यूशन (शुरुआती बाध्यकारी या पारंपरिक आलसी बंधन के साथ पहली कॉल पर) के दौरान एक बार प्रेषण होता है। जैसा कि मैंने कहा, यह सिर्फ ओवरलोडिंग / हुक करता है कि गतिशील लिंकिंग कैसे होती है, न कि फ़ंक्शन को लपेटकर। (विशेष रूप से जीसीसी के ifunc तंत्र के माध्यम से: code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/… )। संबंधित: आधुनिक सीपीयू पर सामान्य विकल्प को याद रखने के __memset_avx2_unaligned_erms लिए इस प्रश्नोत्तर
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.