আমি সবচেয়ে বিশিষ্ট পন্থা তুলনা কিছু মাপকাঠিতে ফলাফল এতদূর উপস্থাপন প্রস্তাব করছি, যথা bobince এর @ findnth()
(উপর ভিত্তি করে str.split()
) বনাম @ tgamblin বা @Mark Byers ' 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()
আমাদের 1.2 s এ একটি উত্তর তুলনায় পেতে পারেনfindnth
47 s এর 'র মোট।
str.find()
ভিত্তিক পদ্ধতির চেয়ে str.split()
ভিত্তিক পদ্ধতির তুলনায় খারাপ অবস্থার এমন কোনও ঘটনা আমি খুঁজে পাইনি , সুতরাং এই মুহুর্তে আমি যুক্তি দেব যে @ টগাম্বলিন বা @ মার্ক বাইয়ার্সের উত্তর @ ববিন্সের পরিবর্তে গ্রহণ করা উচিত।
আমার পরীক্ষায়, 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
। সি কোডটি এখানে একটি সুবিধে খেলছে কারণ এটি একক অক্ষরগুলি সন্ধানের মধ্যে সীমাবদ্ধ, তবে আসুন দেখি এটি কত দ্রুত:
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()
স্পষ্টতই ব্যাকফায়ারিং ...
উপসংহারে, findnth()
(এর উপর ভিত্তি করে str.split()
) বাস্তবায়নটি একটি খারাপ ধারণা, যেহেতু (ক) এটি প্রয়োজনীয় অনুলিপিটির কারণে বৃহত্তর স্ট্রিংয়ের জন্য ভয়ানকভাবে সম্পাদন করে, এবং (খ) এটি mmap.mmap
বস্তুগুলিতে মোটেই কাজ করে না । বাস্তবায়ন find_nth()
(উপর ভিত্তি করে)str.find()
) ক্ষেত্রে সকল পরিস্থিতিতে অগ্রাধিকার দেওয়া উচিত (এবং তাই এই প্রশ্নের গ্রহণযোগ্য উত্তর হতে হবে)।
উন্নয়নের জন্য এখনও বেশ কিছুটা জায়গা রয়েছে, যেহেতু সি এক্সটেনশন খাঁটি পাইথন কোডের চেয়ে প্রায় 4 টির একটি ফ্যাক্টর দৌড়েছিল, এটি ইঙ্গিত করে যে ডেডিকেটেড পাইথন লাইব্রেরি ফাংশনের ক্ষেত্রে কেস থাকতে পারে।