टी एल; डॉ
पाइथन 2 के लिए ओवरहेड को हटाने के बाद वास्तविक गति अंतर 70% (या अधिक) के करीब है।
ऑब्जेक्ट क्रिएशन गलती पर नहीं है। न तो विधि एक नई वस्तु बनाती है, क्योंकि एक-वर्ण तार को कैश किया जाता है।
अंतर स्पष्ट नहीं है, लेकिन संभवत: प्रकार और अच्छी तरह से गठन के संबंध में, स्ट्रिंग इंडेक्सिंग पर अधिक संख्या में चेक से बनाया गया है। यह भी जांचने की आवश्यकता के लिए धन्यवाद है कि क्या लौटना है।
सूची अनुक्रमण उल्लेखनीय रूप से तेज है।
>>> python3 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.388 usec per loop
>>> python3 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.436 usec per loop
आपने जो पाया है, उससे यह असहमत है ...
आप पायथन 2 का उपयोग कर रहे होंगे, तब।
>>> python2 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.309 usec per loop
>>> python2 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.212 usec per loop
आइए संस्करणों के बीच अंतर को स्पष्ट करते हैं। मैं संकलित कोड की जांच करूंगा।
पायथन 3 के लिए:
import dis
def list_iterate():
[item for item in ["a", "b", "c"]]
dis.dis(list_iterate)
#>>> 4 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d06b118a0, file "", line 4>)
#>>> 3 LOAD_CONST 2 ('list_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 3 ('a')
#>>> 12 LOAD_CONST 4 ('b')
#>>> 15 LOAD_CONST 5 ('c')
#>>> 18 BUILD_LIST 3
#>>> 21 GET_ITER
#>>> 22 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 25 POP_TOP
#>>> 26 LOAD_CONST 0 (None)
#>>> 29 RETURN_VALUE
def string_iterate():
[item for item in "abc"]
dis.dis(string_iterate)
#>>> 21 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d06b17150, file "", line 21>)
#>>> 3 LOAD_CONST 2 ('string_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 3 ('abc')
#>>> 12 GET_ITER
#>>> 13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 16 POP_TOP
#>>> 17 LOAD_CONST 0 (None)
#>>> 20 RETURN_VALUE
आप यहाँ देखते हैं कि हर बार सूची के निर्माण के कारण सूची प्रकार धीमा होने की संभावना है।
यह है
9 LOAD_CONST 3 ('a')
12 LOAD_CONST 4 ('b')
15 LOAD_CONST 5 ('c')
18 BUILD_LIST 3
अंश। स्ट्रिंग संस्करण केवल है
9 LOAD_CONST 3 ('abc')
आप देख सकते हैं कि इससे फर्क पड़ता है:
def string_iterate():
[item for item in ("a", "b", "c")]
dis.dis(string_iterate)
#>>> 35 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d068be660, file "", line 35>)
#>>> 3 LOAD_CONST 2 ('string_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 6 (('a', 'b', 'c'))
#>>> 12 GET_ITER
#>>> 13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 16 POP_TOP
#>>> 17 LOAD_CONST 0 (None)
#>>> 20 RETURN_VALUE
यह सिर्फ उत्पादन करता है
9 LOAD_CONST 6 (('a', 'b', 'c'))
के रूप में tuples अपरिवर्तनीय हैं। परीक्षा:
>>> python3 -m timeit '[x for x in ("a", "b", "c")]'
1000000 loops, best of 3: 0.369 usec per loop
महान, गति के लिए वापस।
पायथन 2 के लिए:
def list_iterate():
[item for item in ["a", "b", "c"]]
dis.dis(list_iterate)
#>>> 2 0 BUILD_LIST 0
#>>> 3 LOAD_CONST 1 ('a')
#>>> 6 LOAD_CONST 2 ('b')
#>>> 9 LOAD_CONST 3 ('c')
#>>> 12 BUILD_LIST 3
#>>> 15 GET_ITER
#>>> >> 16 FOR_ITER 12 (to 31)
#>>> 19 STORE_FAST 0 (item)
#>>> 22 LOAD_FAST 0 (item)
#>>> 25 LIST_APPEND 2
#>>> 28 JUMP_ABSOLUTE 16
#>>> >> 31 POP_TOP
#>>> 32 LOAD_CONST 0 (None)
#>>> 35 RETURN_VALUE
def string_iterate():
[item for item in "abc"]
dis.dis(string_iterate)
#>>> 2 0 BUILD_LIST 0
#>>> 3 LOAD_CONST 1 ('abc')
#>>> 6 GET_ITER
#>>> >> 7 FOR_ITER 12 (to 22)
#>>> 10 STORE_FAST 0 (item)
#>>> 13 LOAD_FAST 0 (item)
#>>> 16 LIST_APPEND 2
#>>> 19 JUMP_ABSOLUTE 7
#>>> >> 22 POP_TOP
#>>> 23 LOAD_CONST 0 (None)
#>>> 26 RETURN_VALUE
अजीब बात यह है कि हमारे पास सूची का एक ही भवन है, लेकिन इसके लिए यह अभी भी तेज है। अजगर 2 अजीब तरह से काम कर रहा है।
चलो समझ और फिर से समय निकालें। _ =
इसे बाहर अनुकूलित हो रही रोकने के लिए है।
>>> python3 -m timeit '_ = ["a", "b", "c"]'
10000000 loops, best of 3: 0.0707 usec per loop
>>> python3 -m timeit '_ = "abc"'
100000000 loops, best of 3: 0.0171 usec per loop
हम देख सकते हैं कि संस्करणों के बीच अंतर के लिए प्रारंभिककरण महत्वपूर्ण नहीं है (वे संख्याएं छोटी हैं)! हम इस तरह से निष्कर्ष निकाल सकते हैं कि पायथन 3 में धीमी समझ है। यह समझ में आता है क्योंकि पाइथन 3 ने सुरक्षित स्कोपिंग की संभावना को बदल दिया है।
ठीक है, अब बेंचमार्क में सुधार करें (मैं सिर्फ ओवरहेड को हटा रहा हूं जो पुनरावृत्ति नहीं है)। इसे पूर्व-असाइन करके पुनरावृति के भवन को हटा दिया जाता है:
>>> python3 -m timeit -s 'iterable = "abc"' '[x for x in iterable]'
1000000 loops, best of 3: 0.387 usec per loop
>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
1000000 loops, best of 3: 0.368 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"' '[x for x in iterable]'
1000000 loops, best of 3: 0.309 usec per loop
>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
10000000 loops, best of 3: 0.164 usec per loop
हम जाँच सकते हैं कि क्या कॉल iter
ओवरहेड है:
>>> python3 -m timeit -s 'iterable = "abc"' 'iter(iterable)'
10000000 loops, best of 3: 0.099 usec per loop
>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.1 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"' 'iter(iterable)'
10000000 loops, best of 3: 0.0913 usec per loop
>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.0854 usec per loop
नहीं, यह नहीं है। अंतर बहुत छोटा है, खासकर पायथन 3 के लिए।
तो चलिए अभी और अनचाहे ओवरहेड को हटाते हैं ... पूरी चीज़ को धीमा करके! उद्देश्य बस एक लंबी यात्रा करना है, इसलिए समय अधिक हो जाता है।
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 3.12 msec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.77 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 2.32 msec per loop
>>> python2 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.09 msec per loop
यह वास्तव में ज्यादा नहीं बदला है , लेकिन यह थोड़ा मदद की है।
इसलिए समझ को निकालो। यह ओवरहेड है जो प्रश्न का हिस्सा नहीं है:
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.71 msec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 1.36 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.27 msec per loop
>>> python2 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 935 usec per loop
यह हुई ना बात! हम deque
पुनरावृति का उपयोग करके अभी भी थोड़ा तेज़ हो सकते हैं । यह मूल रूप से एक ही है, लेकिन यह तेज है :
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 805 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop
मुझे जो प्रभावित करता है वह यह है कि यूनीकोड बायट्रस्ट के साथ प्रतिस्पर्धी है। हम इसे स्पष्ट रूप से bytes
और unicode
दोनों में जाँच कर सकते हैं :
bytes
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)).encode("ascii") for _ in range(100000))' 'deque(iterable, maxlen=0)' :(
1000 loops, best of 3: 571 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)).encode("ascii") for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 394 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 757 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop
यहाँ आप पायथन 3 को वास्तव में पायथन 2 की तुलना में तेज़ देखते हैं ।
unicode
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = u"".join( chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 800 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [ chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 394 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = u"".join(unichr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 1.07 msec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = [unichr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 469 usec per loop
फिर से, पायथन 3 तेज है, हालांकि यह उम्मीद की जानी है ( str
पायथन 3 में बहुत ध्यान दिया गया है)।
वास्तव में, यह unicode
- bytes
अंतर बहुत छोटा है, जो प्रभावशाली है।
तो चलिए इस एक मामले का विश्लेषण करते हैं, यह देखते हुए कि यह मेरे लिए तेज़ और सुविधाजनक है:
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
हम वास्तव में टिम पीटर के 10-बार-उत्कीर्ण जवाब से इंकार कर सकते हैं!
>>> foo = iterable[123]
>>> iterable[36] is foo
True
ये कोई नई वस्तु नहीं हैं!
लेकिन यह ध्यान देने योग्य है: अनुक्रमण लागत । अनुक्रमण में अंतर की संभावना होगी, इसलिए पुनरावृत्ति और बस सूचकांक को हटा दें:
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'iterable[123]'
10000000 loops, best of 3: 0.0397 usec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable[123]'
10000000 loops, best of 3: 0.0374 usec per loop
अंतर छोटा लगता है, लेकिन लागत का कम से कम आधा हिस्सा अधिक है:
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable; 123'
100000000 loops, best of 3: 0.0173 usec per loop
इसलिए गति का अंतर इसे दोष देने के लिए पर्याप्त है। मुझे लगता है।
तो सूची को इतनी तेज़ी से अनुक्रमित क्यों किया जा रहा है?
ठीक है, मैं उस पर आपके पास वापस आऊंगा, लेकिन मेरा अनुमान है कि इंटर्न स्ट्रिंग्स (या कैश्ड वर्ण यदि यह एक अलग तंत्र है) के लिए जांच के लिए नीचे है । यह इष्टतम की तुलना में कम तेज होगा। लेकिन मैं स्रोत की जाँच करूँगा (हालाँकि मैं C ... में सहज नहीं हूँ) :)।
तो यहाँ स्रोत है:
static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
void *data;
enum PyUnicode_Kind kind;
Py_UCS4 ch;
PyObject *res;
if (!PyUnicode_Check(self) || PyUnicode_READY(self) == -1) {
PyErr_BadArgument();
return NULL;
}
if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
PyErr_SetString(PyExc_IndexError, "string index out of range");
return NULL;
}
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
if (ch < 256)
return get_latin1_char(ch);
res = PyUnicode_New(1, ch);
if (res == NULL)
return NULL;
kind = PyUnicode_KIND(res);
data = PyUnicode_DATA(res);
PyUnicode_WRITE(kind, data, 0, ch);
assert(_PyUnicode_CheckConsistency(res, 1));
return res;
}
ऊपर से चलते हुए, हमारे पास कुछ जाँचें होंगी। ये उबाऊ हैं। फिर कुछ असाइनमेंट, जो उबाऊ भी होने चाहिए। पहली रोचक पंक्ति है
ch = PyUnicode_READ(kind, data, index);
लेकिन हमें उम्मीद है कि यह तेज़ है, क्योंकि हम इसे अनुक्रमित करके एक सन्निहित सी सरणी से पढ़ रहे हैं। परिणाम, ch
256 से कम होगा, इसलिए हम कैश्ड वर्ण वापस कर देंगेget_latin1_char(ch)
।
तो हम चलेंगे (पहले चेक को छोड़ते हुए)
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return get_latin1_char(ch);
कहाँ पे
#define PyUnicode_KIND(op) \
(assert(PyUnicode_Check(op)), \
assert(PyUnicode_IS_READY(op)), \
((PyASCIIObject *)(op))->state.kind)
(जो उबाऊ है, क्योंकि वाद-विवादों को डिबग में अनदेखा कर दिया जाता है [इसलिए मैं जांच सकता हूं कि वे तेज हैं] और ((PyASCIIObject *)(op))->state.kind)
(मुझे लगता है) एक अप्रत्यक्ष और एक सी-लेवल कास्ट है);
#define PyUnicode_DATA(op) \
(assert(PyUnicode_Check(op)), \
PyUnicode_IS_COMPACT(op) ? _PyUnicode_COMPACT_DATA(op) : \
_PyUnicode_NONCOMPACT_DATA(op))
(जो समान कारणों से भी उबाऊ है, मैक्रोज़ मानते हुए ( Something_CAPITALIZED
सभी तेजी से हैं),
#define PyUnicode_READ(kind, data, index) \
((Py_UCS4) \
((kind) == PyUnicode_1BYTE_KIND ? \
((const Py_UCS1 *)(data))[(index)] : \
((kind) == PyUnicode_2BYTE_KIND ? \
((const Py_UCS2 *)(data))[(index)] : \
((const Py_UCS4 *)(data))[(index)] \
) \
))
(जिसमें इंडेक्स शामिल है लेकिन वास्तव में धीमा नहीं है) और
static PyObject*
get_latin1_char(unsigned char ch)
{
PyObject *unicode = unicode_latin1[ch];
if (!unicode) {
unicode = PyUnicode_New(1, ch);
if (!unicode)
return NULL;
PyUnicode_1BYTE_DATA(unicode)[0] = ch;
assert(_PyUnicode_CheckConsistency(unicode, 1));
unicode_latin1[ch] = unicode;
}
Py_INCREF(unicode);
return unicode;
}
जो मेरे संदेह की पुष्टि करता है:
यह कैश्ड है:
PyObject *unicode = unicode_latin1[ch];
यह उपवास होना चाहिए। if (!unicode)
नहीं चला है, इसलिए यह करने के लिए इस मामले में सचमुच बराबर है
PyObject *unicode = unicode_latin1[ch];
Py_INCREF(unicode);
return unicode;
ईमानदारी से, परीक्षण के बाद assert
तेजी से कर रहे हैं (उन्हें अक्षम करके [मुझे लगता है कि यह सी-लेवल पर काम करता है ...]), केवल प्रशंसनीय-धीमी भागों हैं:
PyUnicode_IS_COMPACT(op)
_PyUnicode_COMPACT_DATA(op)
_PyUnicode_NONCOMPACT_DATA(op)
कौन से:
#define PyUnicode_IS_COMPACT(op) \
(((PyASCIIObject*)(op))->state.compact)
(उपवास, पहले की तरह),
#define _PyUnicode_COMPACT_DATA(op) \
(PyUnicode_IS_ASCII(op) ? \
((void*)((PyASCIIObject*)(op) + 1)) : \
((void*)((PyCompactUnicodeObject*)(op) + 1)))
(तेजी से अगर मैक्रो IS_ASCII
तेज है), और
#define _PyUnicode_NONCOMPACT_DATA(op) \
(assert(((PyUnicodeObject*)(op))->data.any), \
((((PyUnicodeObject *)(op))->data.any)))
(यह भी तेजी के रूप में यह एक जोर प्लस एक परोक्ष प्लस एक डाली है)।
तो हम नीचे हैं (खरगोश छेद):
PyUnicode_IS_ASCII
जो है
#define PyUnicode_IS_ASCII(op) \
(assert(PyUnicode_Check(op)), \
assert(PyUnicode_IS_READY(op)), \
((PyASCIIObject*)op)->state.ascii)
हम्म ... जो तेजी से भी लगता है ...
ठीक है, ठीक है, लेकिन इसकी तुलना करते हैं PyList_GetItem
। (हाँ, मुझे काम करने के लिए और अधिक काम देने के लिए धन्यवाद टिम पीटर्स: पी।)
PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
if (!PyList_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
if (i < 0 || i >= Py_SIZE(op)) {
if (indexerr == NULL) {
indexerr = PyUnicode_FromString(
"list index out of range");
if (indexerr == NULL)
return NULL;
}
PyErr_SetObject(PyExc_IndexError, indexerr);
return NULL;
}
return ((PyListObject *)op) -> ob_item[i];
}
हम देख सकते हैं कि गैर-त्रुटि मामलों पर यह बस चलने वाला है:
PyList_Check(op)
Py_SIZE(op)
((PyListObject *)op) -> ob_item[i]
कहाँ PyList_Check
है
#define PyList_Check(op) \
PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS)
( TABS! TABS !!! ) ( अंक २१५ That ) जो तय हो गया और ५ मिनट में विलीन हो गया । जैसे ... हाँ। अरे नहीं। उन्होंने शर्म करने के लिए स्कीट डाल दिया।
#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size)
#define PyType_FastSubclass(t,f) PyType_HasFeature(t,f)
#ifdef Py_LIMITED_API
#define PyType_HasFeature(t,f) ((PyType_GetFlags(t) & (f)) != 0)
#else
#define PyType_HasFeature(t,f) (((t)->tp_flags & (f)) != 0)
#endif
तो यह आम तौर पर तुच्छ (दो अप्रत्यक्ष और बूलियन जांच के एक जोड़े) जब तक Py_LIMITED_API
है, किस मामले में है ... ???
फिर अनुक्रमण और एक कास्ट ( ((PyListObject *)op) -> ob_item[i]
) है और हम कर रहे हैं।
इसलिए सूचियों के लिए निश्चित रूप से कम जांच होती है, और छोटे गति अंतर निश्चित रूप से इसका मतलब है कि यह प्रासंगिक हो सकता है।
मुझे लगता है कि सामान्य रूप से (->)
, यूनिकोड के लिए बस अधिक प्रकार की जाँच और अप्रत्यक्ष है। ऐसा लगता है कि मुझे एक बिंदु याद आ रहा है, लेकिन क्या ?