python - स्ट्रिंग की शुरुआत धीमी क्यों है?




python-2.7 cpython (2)

हैरानी की बात है, मुझे लगता है कि startswith ही धीमी है:

In [10]: s="ABCD"*10

In [11]: %timeit s.startswith("XYZ")
1000000 loops, best of 3: 307 ns per loop

In [12]: %timeit "XYZ" in s
10000000 loops, best of 3: 81.7 ns per loop

जैसा कि हम सभी जानते हैं, ऑपरेशन में पूरे स्ट्रिंग की खोज करने की आवश्यकता होती है और startswith बस पहले कुछ पात्रों की जांच करने की आवश्यकता होती है, इसलिए startswith को अधिक कुशल होना चाहिए।

जब s काफी बड़ा हो जाता है, तो startswith तेजी से होता है:

In [13]: s="ABCD"*200

In [14]: %timeit s.startswith("XYZ")
1000000 loops, best of 3: 306 ns per loop

In [15]: %timeit "XYZ" in s
1000000 loops, best of 3: 666 ns per loop

तो ऐसा लगता है कि कॉलिंग startswith में कुछ ओवरहेड है जो स्ट्रिंग के छोटे होने पर इसे धीमा कर देता है।

और मैंने यह पता लगाने की कोशिश की कि startswith कॉल का ओवरहेड क्या है।

सबसे पहले, मैंने डॉट ऑपरेशन की लागत को कम करने के लिए एक f वेरिएबल का उपयोग किया - जैसा कि इस answer में बताया गया है - यहां हम देख सकते हैं कि startswith अभी भी धीमी है:

In [16]: f=s.startswith

In [17]: %timeit f("XYZ")
1000000 loops, best of 3: 270 ns per loop

इसके अलावा, मैंने एक खाली फ़ंक्शन कॉल की लागत का परीक्षण किया:

In [18]: def func(a): pass

In [19]: %timeit func("XYZ")
10000000 loops, best of 3: 106 ns per loop

भले ही डॉट ऑपरेशन और फ़ंक्शन कॉल की लागत, startswith का समय लगभग (270-106) = 164ns है, लेकिन ऑपरेशन में केवल 81.7ns लगते हैं। ऐसा लगता है कि अभी भी startswith लिए कुछ ओवरहेड्स हैं, वह क्या है?

startswith और __contains__ द्वारा सुझाए गए __contains__ startswith और __contains__ बीच परीक्षा परिणाम जोड़ें:

In [28]: %timeit s.startswith("XYZ")
1000000 loops, best of 3: 314 ns per loop

In [29]: %timeit s.__contains__("XYZ")
1000000 loops, best of 3: 192 ns per loop

इसकी संभावना है क्योंकि str.startswith() str.__contains__() से अधिक करता है str.__contains__() , और इसलिए भी कि मेरा मानना ​​है कि str.__contains__ str.startswith() C में पूरी तरह से संचालित str.__contains__ है, जबकि str.startswith() को पायथन प्रकारों के साथ इंटरैक्ट करना पड़ता है। इसका हस्ताक्षर str.startswith(prefix[, start[, end]]) , जहां उपसर्ग कोशिश करने के लिए तार का एक str.startswith(prefix[, start[, end]]) हो सकता है।


जैसा कि टिप्पणियों में पहले ही उल्लेख किया गया है, यदि आप s.__contains__("XYZ") उपयोग करते हैं, तो s.__contains__("XYZ") आपको s.startswith("XYZ") समान है क्योंकि इसमें समान मार्ग लेने की आवश्यकता है: सदस्य स्ट्रिंग ऑब्जेक्ट पर खोज करते हैं , एक फ़ंक्शन कॉल के बाद। यह आमतौर पर कुछ महंगा है (पर्याप्त नहीं है कि आपको पाठ्यक्रम के बारे में चिंता करनी चाहिए)। दूसरी ओर, जब आप "XYZ" in s करते हैं, तो पार्सर ऑपरेटर की व्याख्या करता है और सदस्य की पहुंच को __contains__ तक कम कर सकता है (या इसके पीछे कार्यान्वयन नहीं कर सकता है, क्योंकि __contains__ खुद को लागू करने का सिर्फ एक तरीका है) ।

आप इसके बारे में एक विचार प्राप्त कर सकते हैं, जो कि बायोटेक को देखकर है:

>>> dis.dis('"XYZ" in s')
  1           0 LOAD_CONST               0 ('XYZ')
              3 LOAD_NAME                0 (s)
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
>>> dis.dis('s.__contains__("XYZ")')
  1           0 LOAD_NAME                0 (s)
              3 LOAD_ATTR                1 (__contains__)
              6 LOAD_CONST               0 ('XYZ')
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 RETURN_VALUE

इसलिए s.__contains__("XYZ") तुलना में s.__contains__("XYZ") s.startswith("XYZ") s.__contains__("XYZ") साथ s.__contains__("XYZ") , और अधिक समान परिणाम देगा, हालांकि आपके उदाहरण string s , startswith अभी भी धीमी होगी।

उस पर जाने के लिए, आप दोनों के कार्यान्वयन की जांच कर सकते हैं। इसमें शामिल कार्यान्वयन के लिए दिलचस्प यह है कि यह सांख्यिकीय रूप से टाइप किया गया है, और केवल यह मानता है कि तर्क एक यूनिकोड ऑब्जेक्ट है। तो यह काफी कुशल है।

startswith कार्यान्वयन हालांकि एक "गतिशील" पायथन विधि है जिसे लागू करने के लिए वास्तव में तर्कों को पार्स करने की आवश्यकता होती है। startswith भी एक तर्क के रूप में टपल का समर्थन करता है, जो विधि के पूरे स्टार्ट-अप को थोड़ा धीमा बनाता है: (मेरे द्वारा संक्षिप्त, मेरी टिप्पणियों के साथ):

static PyObject * unicode_startswith(PyObject *self, PyObject *args)
{
    // argument parsing
    PyObject *subobj;
    PyObject *substring;
    Py_ssize_t start = 0;
    Py_ssize_t end = PY_SSIZE_T_MAX;
    int result;
    if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end))
        return NULL;

    // tuple handling
    if (PyTuple_Check(subobj)) {}

    // unicode conversion
    substring = PyUnicode_FromObject(subobj);
    if (substring == NULL) {}

    // actual implementation
    result = tailmatch(self, substring, start, end, -1);
    Py_DECREF(substring);
    if (result == -1)
        return NULL;
    return PyBool_FromLong(result);
}

यह संभवतः एक बड़ा कारण है कि startswith स्ट्रिंग्स के लिए धीमी है, जिसके लिए एक सादगी की वजह से तेज है।








startswith