मैं अब तक प्रस्तुत सबसे प्रमुख दृष्टिकोणों की तुलना में कुछ बेंचमार्किंग परिणाम पेश कर रहा हूं, जिसका नाम @ bobince findnth()
(पर आधारित str.split()
) बनाम @ tgamblin's या @Mark बायर्स ' find_nth()
(पर आधारित str.find()
) है। मैं सी एक्सटेंशन ( _find_nth.so
) के साथ तुलना करके देखूंगा कि हम कितनी तेजी से जा सकते हैं। यहाँ है find_nth.py
:
def findnth(haystack, needle, n):
parts= haystack.split(needle, n+1)
if len(parts)<=n+1:
return -1
return len(haystack)-len(parts[-1])-len(needle)
def find_nth(s, x, n=0, overlap=False):
l = 1 if overlap else len(x)
i = -l
for c in xrange(n + 1):
i = s.find(x, i + l)
if i < 0:
break
return i
बेशक, प्रदर्शन सबसे ज्यादा मायने रखता है अगर स्ट्रिंग बड़ी है, तो मान लीजिए कि हम 'बिगफाइल' नामक 1.3 जीबी फाइल में 1000001 वीं नईलाइन ('\ n') को ढूंढना चाहते हैं। मेमोरी को बचाने के लिए, हम mmap.mmap
फ़ाइल के ऑब्जेक्ट प्रतिनिधित्व पर काम करना चाहेंगे :
In [1]: import _find_nth, find_nth, mmap
In [2]: f = open('bigfile', 'r')
In [3]: mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
ऑब्जेक्ट्स का समर्थन नहीं करने के findnth()
बाद से पहले से ही पहली समस्या है । इसलिए हमें वास्तव में पूरी फाइल को मेमोरी में कॉपी करना होगा:mmap.mmap
split()
In [4]: %time s = mm[:]
CPU times: user 813 ms, sys: 3.25 s, total: 4.06 s
Wall time: 17.7 s
आउच! सौभाग्य से s
अभी भी मेरी मैकबुक एयर की 4 जीबी मेमोरी में फिट है, तो चलो मानदंड findnth()
:
In [5]: %timeit find_nth.findnth(s, '\n', 1000000)
1 loops, best of 3: 29.9 s per loop
स्पष्ट रूप से एक भयानक प्रदर्शन। आइए देखें कि यह कैसे str.find()
करता है:
In [6]: %timeit find_nth.find_nth(s, '\n', 1000000)
1 loops, best of 3: 774 ms per loop
काफी बेहतर! स्पष्ट रूप से, findnth()
समस्या यह है कि यह स्ट्रिंग को कॉपी करने के लिए मजबूर किया जाता है split()
, जो पहले से ही दूसरी बार है जब हमने 1.3 जीबी डेटा की प्रतिलिपि बनाई s = mm[:]
। यहां दूसरा फायदा मिलता है find_nth()
: हम इसे mm
सीधे उपयोग कर सकते हैं , जैसे कि फ़ाइल की शून्य प्रतियां आवश्यक हैं:
In [7]: %timeit find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 1.21 s per loop
ऐसा प्रतीत होता है कि यह एक छोटा सा प्रदर्शन है , जो mm
बनाम पर चल रहा है s
, लेकिन यह दिखाता है कि find_nth()
हमें findnth
47 के कुल योग की तुलना में 1.2 सेकंड में जवाब मिल सकता है ।
मुझे ऐसा कोई मामला नहीं मिला जहां str.find()
आधारित दृष्टिकोण आधारित दृष्टिकोण से काफी खराब था str.split()
, इसलिए इस बिंदु पर, मैं तर्क दूंगा कि @ tgamblin या @Mark बायर्स का उत्तर @ bobince के बजाय स्वीकार किया जाना चाहिए।
मेरे परीक्षण में, find_nth()
ऊपर का संस्करण सबसे तेज शुद्ध पायथन समाधान था जो मैं (@ बार्क बायर्स संस्करण के समान) के साथ आ सकता था। आइए देखें कि हम सी एक्सटेंशन मॉड्यूल के साथ कितना बेहतर कर सकते हैं। यहाँ है _find_nthmodule.c
:
#include <Python.h>
#include <string.h>
off_t _find_nth(const char *buf, size_t l, char c, int n) {
off_t i;
for (i = 0; i < l; ++i) {
if (buf[i] == c && n-- == 0) {
return i;
}
}
return -1;
}
off_t _find_nth2(const char *buf, size_t l, char c, int n) {
const char *b = buf - 1;
do {
b = memchr(b + 1, c, l);
if (!b) return -1;
} while (n--);
return b - buf;
}
/* mmap_object is private in mmapmodule.c - replicate beginning here */
typedef struct {
PyObject_HEAD
char *data;
size_t size;
} mmap_object;
typedef struct {
const char *s;
size_t l;
char c;
int n;
} params;
int parse_args(PyObject *args, params *P) {
PyObject *obj;
const char *x;
if (!PyArg_ParseTuple(args, "Osi", &obj, &x, &P->n)) {
return 1;
}
PyTypeObject *type = Py_TYPE(obj);
if (type == &PyString_Type) {
P->s = PyString_AS_STRING(obj);
P->l = PyString_GET_SIZE(obj);
} else if (!strcmp(type->tp_name, "mmap.mmap")) {
mmap_object *m_obj = (mmap_object*) obj;
P->s = m_obj->data;
P->l = m_obj->size;
} else {
PyErr_SetString(PyExc_TypeError, "Cannot obtain char * from argument 0");
return 1;
}
P->c = x[0];
return 0;
}
static PyObject* py_find_nth(PyObject *self, PyObject *args) {
params P;
if (!parse_args(args, &P)) {
return Py_BuildValue("i", _find_nth(P.s, P.l, P.c, P.n));
} else {
return NULL;
}
}
static PyObject* py_find_nth2(PyObject *self, PyObject *args) {
params P;
if (!parse_args(args, &P)) {
return Py_BuildValue("i", _find_nth2(P.s, P.l, P.c, P.n));
} else {
return NULL;
}
}
static PyMethodDef methods[] = {
{"find_nth", py_find_nth, METH_VARARGS, ""},
{"find_nth2", py_find_nth2, METH_VARARGS, ""},
{0}
};
PyMODINIT_FUNC init_find_nth(void) {
Py_InitModule("_find_nth", methods);
}
यहाँ setup.py
फ़ाइल है:
from distutils.core import setup, Extension
module = Extension('_find_nth', sources=['_find_nthmodule.c'])
setup(ext_modules=[module])
हमेशा की तरह स्थापित करें python setup.py install
। C कोड यहाँ एक लाभ पर खेलता है क्योंकि यह एकल वर्णों को खोजने तक सीमित है, लेकिन देखते हैं कि यह कितना तेज़ है:
In [8]: %timeit _find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 218 ms per loop
In [9]: %timeit _find_nth.find_nth(s, '\n', 1000000)
1 loops, best of 3: 216 ms per loop
In [10]: %timeit _find_nth.find_nth2(mm, '\n', 1000000)
1 loops, best of 3: 307 ms per loop
In [11]: %timeit _find_nth.find_nth2(s, '\n', 1000000)
1 loops, best of 3: 304 ms per loop
स्पष्ट रूप से अभी भी थोड़ा बहुत तेज है। दिलचस्प बात यह है कि इन-मेमरी और एमएमपीड मामलों के बीच सी स्तर पर कोई अंतर नहीं है। यह भी देखना होगा कि दिलचस्प है _find_nth2()
, जो पर आधारित है string.h
के memchr()
पुस्तकालय समारोह, में सीधा कार्यान्वयन के खिलाफ खो देता है _find_nth()
: में अतिरिक्त "अनुकूलन" memchr()
जाहिरा तौर पर backfiring कर रहे हैं ...
अंत में, findnth()
(पर आधारित str.split()
) में कार्यान्वयन वास्तव में एक बुरा विचार है, क्योंकि (ए) यह आवश्यक प्रतिलिपि के कारण बड़े तारों के लिए बहुत अच्छा प्रदर्शन करता है, और (बी) यह mmap.mmap
वस्तुओं पर बिल्कुल काम नहीं करता है । सभी परिस्थितियों में कार्यान्वयन find_nth()
(पर आधारित str.find()
) को प्राथमिकता दी जानी चाहिए (और इसलिए इस प्रश्न का स्वीकृत उत्तर होना चाहिए)।
सुधार के लिए अभी भी काफी जगह है, क्योंकि सी एक्सटेंशन शुद्ध पायथन कोड की तुलना में लगभग 4 गुना तेज है, यह दर्शाता है कि एक समर्पित पायथन लाइब्रेरी फ़ंक्शन के लिए एक मामला हो सकता है।