2017-09-12 15 views
6

मैं दो अनुक्रमों की तत्व-वार तुलना में शुद्ध-पायथन (बाहरी निर्भरताओं के बिना) बनाने की कोशिश कर रहा था। मेरा पहला समाधान था:मानचित्र बनाम starmap का प्रदर्शन?

list(map(operator.eq, seq1, seq2)) 

तब मैं itertools से starmap समारोह पाया है, जो बहुत मेरे लिए समान लग रहा था। लेकिन यह मेरे कंप्यूटर पर सबसे खराब मामले में 37% तेज हो गया। के रूप में यह मेरे लिए स्पष्ट नहीं था, मैं एक जनरेटर से 1 तत्व पुनः प्राप्त करने के समय आवश्यक मापा जाता है (यदि इस तरह से सही है पता नहीं है):

from operator import eq 
from itertools import starmap 

seq1 = [1,2,3]*10000 
seq2 = [1,2,3]*10000 
seq2[-1] = 5 

gen1 = map(eq, seq1, seq2)) 
gen2 = starmap(eq, zip(seq1, seq2)) 

%timeit -n1000 -r10 next(gen1) 
%timeit -n1000 -r10 next(gen2) 

271 ns ± 1.26 ns per loop (mean ± std. dev. of 10 runs, 1000 loops each) 
208 ns ± 1.72 ns per loop (mean ± std. dev. of 10 runs, 1000 loops each) 

तत्वों दूसरा समाधान पुन: प्राप्त करने में 24% अधिक performant है । उसके बाद, वे दोनों list के लिए एक ही परिणाम उत्पन्न करते हैं। लेकिन कहीं से हम समय में अतिरिक्त 13% लाभ:

%timeit list(map(eq, seq1, seq2)) 
%timeit list(starmap(eq, zip(seq1, seq2))) 

5.24 ms ± 29.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 
3.34 ms ± 84.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 

मैं ऐसे नेस्टेड कोड की रूपरेखा में बेहतर जानकारी के लिए पता नहीं कैसे? तो मेरा सवाल यह है कि पहला जनरेटर पुनः प्राप्त करने में इतनी तेजी से क्यों और list फ़ंक्शन में हमें अतिरिक्त 13% प्राप्त करने का तरीका क्यों है?

संपादित करें: मेरी पहली इरादा, तत्व के लिहाज से all के बजाय तुलना प्रदर्शन करने के लिए था, इसलिए all समारोह list से बदला गया। यह प्रतिस्थापन समय अनुपात को प्रभावित नहीं करता है।

CPython 3.6.2 Windows 10 (64 बिट) पर

+1

बस का प्रयोग क्यों न 'seq1 = = seq2'? –

+0

@ Błotosmętek सुधार के लिए धन्यवाद! मेरा पहला इरादा 'सभी' के बजाय तत्व-वार तुलना था, जो मेरे प्रश्न से स्पष्ट नहीं था :) वास्तव में यदि आप 'सभी' के बजाय 'सूची' को प्रतिस्थापित करते हैं तो समय के क्रम समान होंगे। – godaygo

+0

क्या पायथन संस्करण? और यह सीपीथन है? – MSeifert

उत्तर

2

कई कारक योगदान (संयोजन के रूप में) मनाया प्रदर्शन अंतर करने के लिए कर रहे हैं:

  • zip फिर से उपयोग करता लौटे tuple अगर यह 1 के एक संदर्भ संख्या अधिक है जब अगले __next__ कॉल से बना।
  • mapनयाtuple बनाता है जो __next__ कॉल हर बार "मैप किए गए फ़ंक्शन" को पास किया जाता है। दरअसल यह शायद स्क्रैच से एक नया टुपल नहीं बनाएगा क्योंकि पायथन अप्रयुक्त टुपल्स के लिए भंडारण बनाए रखता है। लेकिन उस स्थिति में map को सही आकार के एक अप्रयुक्त ट्यूपल को ढूंढना है।
  • starmap जांचता है कि पुन: प्रयोज्य में अगला आइटम tuple प्रकार है और यदि ऐसा है तो यह बस इसे पास करता है।
  • PyObject_Call के साथ सी कोड के भीतर से एक सी फ़ंक्शन को कॉल करने से कैली में पारित एक नया टुपल नहीं बन जाएगा।

तो starmapzip के साथ केवल एक टपल बार बार का उपयोग करेगा कि operator.eq में भेजा जाता है इस प्रकार बेहद समारोह कॉल उपरि कम करने। दूसरी तरफ map हर बार operator.eq कहा जाता है, एक नया ट्यूपल (या सीपीरॉन 3.6 से सी सर भरें) बना देगा। तो वास्तव में गति अंतर वास्तव में टुपल निर्माण ओवरहेड है। ,

In [1]: %load_ext cython 

In [2]: %%cython 
    ...: 
    ...: from cpython.ref cimport Py_DECREF 
    ...: 
    ...: cpdef func(zipper): 
    ...:  a = next(zipper) 
    ...:  print('a', a) 
    ...:  Py_DECREF(a) 
    ...:  b = next(zipper) 
    ...:  print('a', a) 

In [3]: func(zip([1, 2], [1, 2])) 
a (1, 1) 
a (2, 2) 

हाँ, tuple रों वास्तव में अपरिवर्तनीय नहीं हैं एक सरल Py_DECREF करने के लिए पर्याप्त था ":

इसके बजाय स्रोत कोड को जोड़ने की मैं कुछ Cython कोड है कि इस सत्यापित करने के लिए इस्तेमाल किया जा सकता प्रदान करेंगे ट्रिक "zip किसी और को विश्वास करने के लिए लौटा हुआ ट्यूपल का संदर्भ नहीं है!

"टपल-पारित होना" के लिए के रूप में:

In [4]: %%cython 
    ...: 
    ...: def func_inner(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: def func(*args): 
    ...:  print(id(args)) 
    ...:  func_inner(*args) 

In [5]: func(1, 2) 
1404350461320 
1404350461320 

तो टपल सही के माध्यम से (सिर्फ इसलिए कि इन सी कार्यों के रूप में परिभाषित कर रहे हैं!) यह शुद्ध पायथन कार्यों के लिए नहीं होता है पारित हो जाता है:

In [6]: def func_inner(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: def func(*args): 
    ...:  print(id(args)) 
    ...:  func_inner(*args) 
    ...: 

In [7]: func(1, 2) 
1404350436488 
1404352833800 

ध्यान दें कि यह भी अगर बुलाया समारोह एक सी समारोह एक सी समारोह से कहा जाता है, भले ही नहीं है ऐसा नहीं होता है:

In [8]: %%cython 
    ...: 
    ...: def func_inner_c(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: def func(inner, *args): 
    ...:  print(id(args)) 
    ...:  inner(*args) 
    ...: 

In [9]: def func_inner_py(*args): 
    ...:  print(id(args)) 
    ...: 
    ...: 

In [10]: func(func_inner_py, 1, 2) 
1404350471944 
1404353010184 

In [11]: func(func_inner_c, 1, 2) 
1404344354824 
1404344354824 

तो वहाँ "संयोग" मुद्दा यह है कि starmapzip के साथ कई तर्क के साथ map बुला की तुलना में तेजी है बुलाया समारोह भी एक सी समारोह है जब करने के लिए अग्रणी के एक बहुत हैं ...

1

एक अंतर यह मैं देख सकते हैं कि कैसे map iterables से आइटम को पुन: प्राप्त है। map और zip दोनों पुनरावृत्त उत्तीर्ण से इटेटरेटर्स का एक टुपल बनाएं। अब zip एक result tuple आंतरिक रूप से बनाए रखता है जो हर बार अगली बार कॉल किया जाता है और दूसरी तरफ map creates a new array* प्रत्येक अगली कॉल के साथ और इसे हटा देता है।


* के रूप में 3.5.4 map_next एक नया अजगर टपल हर आवंटित करने के लिए प्रयोग किया जाता है जब तक MSeifert से बताया। यह 3.6 और 5 तक बदल गया है। सी स्टैक का उपयोग किया जाता है और उस ढेर से बड़े किसी भी चीज़ के लिए उपयोग किया जाता है। संबंधित पीआरएस: Issue #27809: map_next() uses fast call और Add _PY_FASTCALL_SMALL_STACK constant | मुद्दा: https://bugs.python.org/issue27809

+0

पर सीपीथन 3.6.2 यह मान लेगा कि यह 3.6 है, ध्यान दें कि [3.5.4] में कोड (https://github.com/python/cpython/blob/v3 .5.4/पायथन/bltinmodule.C# L1168-L1192) अलग दिखता है। :) – MSeifert

+0

@MSeifert मुझे आश्चर्य है कि धीमी गति से 3.5.4 कार्यान्वयन की तुलना 3.6.2 से की गई थी। –

+0

(ढेर बनाम सी-स्टैक पहुंच के कारण 5 पुनरावृत्तियों तक धीमा होना चाहिए) –

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