起步
对于子串搜索,Python提供了多种实现方式: in
, find
, index
, __contains__
,对其进行性能比较:
import timeit def in_(s, other): return other in s def contains(s, other): return s.__contains__(other) def find(s, other): return s.find(other) != -1 def index(s, other): try: s.index(other) except ValueError: return False return True perf_dict = { 'in:True': min(timeit.repeat(lambda: in_('superstring', 'str'))), 'in:False': min(timeit.repeat(lambda: in_('superstring', 'not'))), '__contains__:True': min(timeit.repeat(lambda: contains('superstring', 'str'))), '__contains__:False': min(timeit.repeat(lambda: contains('superstring', 'not'))), 'find:True': min(timeit.repeat(lambda: find('superstring', 'str'))), 'find:False': min(timeit.repeat(lambda: find('superstring', 'not'))), 'index:True': min(timeit.repeat(lambda: index('superstring', 'str'))), 'index:False': min(timeit.repeat(lambda: index('superstring', 'not'))), } print(perf_dict)
得到结果:
从结果上 in
的搜索方式性能上最好。
知其然也要之其所以然,下面就对于这个结果进行比较与分析。
in
与 __contains__
比较
了解 Python 中协议的应该知道, in
操作其实也是调用 __contains__
,但为什么 in
比 __contains__
明显快了很多,明明它们最终调用的C语言函数是一样的。
在 CPython 中, in
属于操作符,它直接指向了 sq_contains
中的C级函数指针,而在 str
中的 sq_contains
直接指向了最终调用的C层函数。而 __contains__
的调用方式,则需要先在str
属性中进行 LOAD_ATTR
查找,然后再为 CALL_FUNCTION
创建函数调用所需的空间。
也就是说, in
直接指向了最终的C层函数,一步到位,也不走Python虚拟机的函数调用,而__contains__
调用方式先属性查找和Python函数调用的开销;所以 str.__contains__(other)
的形式要慢得多。
一般来说, in
方式更快只使用 Python 内置的C实现的类。对于用户自定义类,因为最终调用都是Python级的,所以两种方式都要对函数调用所需的空间的。
find
与 index
的比较
find
与 index
的查找方式的区别仅仅只是 index
在子串不存在时会抛出异常。从源码来看:
static PyObject * unicode_find(PyObject *self, PyObject *args) { /* initialize variables to prevent gcc warning */ PyObject *substring = NULL; Py_ssize_t start = 0; Py_ssize_t end = 0; Py_ssize_t result; if (!parse_args_finds_unicode("find", args, &substring, &start, &end)) return NULL; if (PyUnicode_READY(self) == -1) return NULL; result = any_find_slice(self, substring, start, end, 1); if (result == -2) return NULL; return PyLong_FromSsize_t(result); } static PyObject * unicode_index(PyObject *self, PyObject *args) { /* initialize variables to prevent gcc warning */ Py_ssize_t result; PyObject *substring = NULL; Py_ssize_t start = 0; Py_ssize_t end = 0; if (!parse_args_finds_unicode("index", args, &substring, &start, &end)) return NULL; if (PyUnicode_READY(self) == -1) return NULL; result = any_find_slice(self, substring, start, end, 1); if (result == -2) return NULL; if (result < 0) { PyErr_SetString(PyExc_ValueError, "substring not found"); return NULL; } return PyLong_FromSsize_t(result); }
实现方式基本相同,所以在子串存在的时候,两者的性能一致;而当子串不存在时, index
会设置异常,因此涉及异常栈的空间等异常机制,速度上也就慢了一些。