Şimdiye kadar sunulan en önemli yaklaşımları, yani @ bobince's findnth()(dayalı str.split()) ile @ tgamblin's veya @Mark Byers ' find_nth()(dayalı str.find()) gibi bazı kıyaslama sonuçları sunuyorum . Ayrıca _find_nth.sone kadar hızlı gidebileceğimizi görmek için bir C uzantısı ( ) ile karşılaştıracağım . İşte 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
Elbette, dizge büyükse performans en çok önemlidir, bu nedenle 1000001. yeni satırı ('\ n') 'bigfile' adlı 1.3 GB'lik bir dosyada bulmak istediğimizi varsayalım. Hafızadan tasarruf etmek için mmap.mmap, dosyanın nesne temsili üzerinde çalışmak istiyoruz :
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)
İle ilk sorun zaten var findnth()olduğundan, mmap.mmapnesneler desteklemez split(). Yani aslında tüm dosyayı belleğe kopyalamalıyız:
In [4]: %time s = mm[:]
CPU times: user 813 ms, sys: 3.25 s, total: 4.06 s
Wall time: 17.7 s
Ah! Neyse ki shala Macbook Air'imin 4 GB belleğine sığıyor, öyleyse kıyaslama yapalım findnth():
In [5]: %timeit find_nth.findnth(s, '\n', 1000000)
1 loops, best of 3: 29.9 s per loop
Açıkça korkunç bir performans. Temel alınan yaklaşımın nasıl olduğunu görelim str.find():
In [6]: %timeit find_nth.find_nth(s, '\n', 1000000)
1 loops, best of 3: 774 ms per loop
Çok daha iyi! Açıkçası findnth()sorun, dizeyi kopyalamak zorunda kalmasıdır split(), bu da 1.3 GB'lık veriyi ikinci kez kopyaladık s = mm[:]. İşte ikinci avantajı find_nth(): Dosyanın sıfır kopyası gerekecek şekilde mmdoğrudan kullanabiliriz :
In [7]: %timeit find_nth.find_nth(mm, '\n', 1000000)
1 loops, best of 3: 1.21 s per loop
Buna mmkarşı işleyen küçük bir performans cezası var gibi görünüyor s, ancak bu find_nth()bize findnthtoplam 47 saniyeye kıyasla 1,2 saniyede bir cevap alabileceğimizi gösteriyor .
Temel yaklaşımın temelli yaklaşımdan str.find()önemli ölçüde daha kötü olduğu bir durum bulamadım str.split(), bu nedenle bu noktada @ bobince'nin yerine @ tgamblin'in veya @Mark Byers'ın yanıtının kabul edilmesi gerektiğini savunabilirim.
Testlerimde, find_nth()yukarıdaki sürüm bulabildiğim en hızlı saf Python çözümüydü (@ Mark Byers'ın sürümüne çok benzer). Bakalım bir C genişletme modülü ile ne kadar iyi yapabiliriz. İşte _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);
}
İşte setup.pydosya:
from distutils.core import setup, Extension
module = Extension('_find_nth', sources=['_find_nthmodule.c'])
setup(ext_modules=[module])
İle her zamanki gibi yükleyin python setup.py install. Tek karakter bulmakla sınırlı olduğu için burada C kodu bir avantaj sağlar, ancak bunun ne kadar hızlı olduğunu görelim:
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
Açıkça biraz daha hızlı. İlginç bir şekilde, bellek içi ve genişletilmiş kasalar arasında C düzeyinde bir fark yoktur. Ayrıca, 'nin kütüphane işlevine _find_nth2()dayanan , basit uygulamaya karşı kaybettiğini görmek ilginçtir : içindeki ek "optimizasyonlar" görünüşte geri tepiyor ...string.hmemchr()_find_nth()memchr()
Sonuç olarak, findnth()(dayalı str.split()) içindeki uygulama gerçekten kötü bir fikirdir, çünkü (a) gerekli kopyalama nedeniyle daha büyük dizeler için korkunç bir performans gösterir ve (b) mmap.mmapnesneler üzerinde hiç çalışmaz . İçinde uygulanması find_nth()(dayanarak str.find()) her koşulda tercih edilmelidir (ve dolayısıyla bu sorunun kabul cevabı).
C uzantısı, saf Python kodundan neredeyse 4 kat daha hızlı çalıştığı için, özel bir Python kütüphanesi işlevi için bir durum olabileceğine işaret ettiğinden, iyileştirme için hala epeyce alan var.