Python 3中字符串的三向比较
假设您想要优化用Python实现的(字节)字符串比较密集型算法。因为中心代码路径包含这一系列语句Python 3中字符串的三向比较,python,string,python-3.x,Python,String,Python 3.x,假设您想要优化用Python实现的(字节)字符串比较密集型算法。因为中心代码路径包含这一系列语句 if s < t: # less than ... elif t < s: # greater than ... else: # equal ... 如果sa))。这将为您提供r,但是。。。这仍然是两个比较。我尝试了一个基于python的bytes\u compare实现。。。非常糟糕的主意:)至少3倍的速度注:为了使这一点在几乎零开销的情况下更易于重用,用更通
if s < t:
# less than ...
elif t < s:
# greater than ...
else:
# equal ...
如果s
它将是伟大的,以优化它的东西,如
r = bytes_compare(s, t)
if r < 0:
# less than ...
elif r > 0:
# greater than ...
else:
# equal ...
r=bytes\u比较(s,t)
如果r<0:
#少于。。。
如果r>0:
#大于。。。
其他:
#相等的。。。
其中(假设的)bytes\u compare()
理想情况下只调用C函数,该函数通常经过了很好的优化。这将使字符串比较的数量减少一半。除非字符串超短,否则这是一个非常可行的优化
但是如何使用Python 3实现这一目标呢
PS:
Python3删除了三向比较全局函数cmp()
和神奇的方法\uuu cmp\uu()
。即使使用Python2,bytes
类也没有\uuu cmp\uuu()
成员
使用ctypes
包可以直接调用memcmp()
,但是ctypes
的外部函数调用开销非常高。Python3(包括3.6)根本不包括对字符串的任何三向比较支持。尽管富比较运算符、的内部实现确实调用了memcmp()
(在bytes
-cf.对象/bytesobject.C
的C实现中)但没有可以利用的内部三向比较函数
因此,通过调用memcmp()
编写一个提供三向比较函数的
#include <Python.h>
static PyObject* cmp(PyObject* self, PyObject* args) {
PyObject *a = 0, *b = 0;
if (!PyArg_UnpackTuple(args, "cmp", 2, 2, &a, &b))
return 0;
if (!PyBytes_Check(a) || !PyBytes_Check(b)) {
PyErr_SetString(PyExc_TypeError, "only bytes() strings supported");
return 0;
}
Py_ssize_t n = PyBytes_GET_SIZE(a), m = PyBytes_GET_SIZE(b);
char *s = PyBytes_AsString(a), *t = PyBytes_AsString(b);
int r = 0;
if (n == m) {
r = memcmp(s, t, n);
} else if (n < m) {
r = memcmp(s, t, n);
if (!r)
r = -1;
} else {
r = memcmp(s, t, m);
if (!r)
r = 1;
}
return PyLong_FromLong(r);
}
static PyMethodDef bytes_util_methods[] = {
{ "cmp", cmp, METH_VARARGS, "Three way compare 2 bytes() objects." },
{0,0,0,0} };
static struct PyModuleDef bytes_util_def = {
PyModuleDef_HEAD_INIT, "bytes_util", "Three way comparison for strings.",
-1, bytes_util_methods };
PyMODINIT_FUNC PyInit_bytes_util(void) {
Py_Initialize();
return PyModule_Create(&bytes_util_def);
}
测试:
与通过ctypes
包调用memcmp
不同,这个外部调用与内置字节比较运算符具有相同的开销(因为它们在标准Python版本中也是作为C扩展实现的)。听起来像是在Python的循环中进行字符串比较。这将永远是缓慢的。您最好使用Cython、Pandas等相关工具设计一个合适的快速解决方案。但这样做需要对周围的代码进行全面检查,而不是对单个字符串进行微基准比较。比较是如何实现的,鉴于s
和t
是字节
?@JohnZwinck,我用pyflame做了一些分析,这验证了在这些成对比较中花费了大量时间(s
与t
之后是t
与s
)。请注意,这种模式并不罕见——例如,当您合并/连接2个排序的字符串序列时,也会出现这种情况。此外,Python字符串比较通常并不慢——如果您只需要对s
和t
进行一次比较,那么它们的速度也很快。因此,这并不是孤立的微基准标记。@urban,在Python3.6.5中,有对象/bytesobject.c
,它在bytes\u compare\u eq()
和bytes\u richcompare()
(对于
)中调用memcmp()
。在Python3中讨论cmp()
,建议的方法是:((a>b)-(b>a))
。这将为您提供r
,但是。。。这仍然是两个比较。我尝试了一个基于python的bytes\u compare
实现。。。非常糟糕的主意:)至少3倍的速度注:为了使这一点在几乎零开销的情况下更易于重用,用更通用的缓冲区接口(带有标志
)取代PyBytes
API的显式使用将允许它与bytearray
,mmap
)无缝工作,array.array('B')
,以及所有其他类似字节的对象。您只需在堆栈上声明一对Py\u buffer
结构,用w/PyObject\u GetBuffer
和您的输入填充它们,然后在当前使用s/t
和m/n
的位置使用buf.buf
和buf.len
。
gcc -Wall -O3 -fPIC -shared bytes_util.c -o bytes_util.so -I/usr/include/python3.6m
>>> import bytes_util
>>> bytes_util.cmp(b'foo', b'barx')
265725