为什么string的开始慢于?

令人惊讶的是,我发现启动比in

 In [10]: s="ABCD"*10 In [11]: %timeit s.startswith("XYZ") 1000000 loops, best of 3: 307 ns per loop In [12]: %timeit "XYZ" in s 10000000 loops, best of 3: 81.7 ns per loop 

众所周知, in操作中需要search整个string,并且只需要检查前几个字符,因此startswith应该更有效率。

s足够大时,启动速度更快:

 In [13]: s="ABCD"*200 In [14]: %timeit s.startswith("XYZ") 1000000 loops, best of 3: 306 ns per loop In [15]: %timeit "XYZ" in s 1000000 loops, best of 3: 666 ns per loop 

所以看起来,调用startswith有一些开销,这使得string很小时,它会变慢。

而且我试图找出什么是startswith调用的开销。

首先,我使用了一个fvariables来降低点操作的成本 – 正如在这个答案中提到的 – 在这里我们可以看到startswith的速度还是比较慢的:

 In [16]: f=s.startswith In [17]: %timeit f("XYZ") 1000000 loops, best of 3: 270 ns per loop 

此外,我testing了一个空函数的成本:

 In [18]: def func(a): pass In [19]: %timeit func("XYZ") 10000000 loops, best of 3: 106 ns per loop 

不pipe点操作和函数调用的成本如何,启动时间约为(270-106)= 164ns,但运行时间只有81.7ns。 这似乎还有一些overheadwith的开销,那是什么?

按照poke和lvc的build议在startwith和__contains__之间添加testing结果:

 In [28]: %timeit s.startswith("XYZ") 1000000 loops, best of 3: 314 ns per loop In [29]: %timeit s.__contains__("XYZ") 1000000 loops, best of 3: 192 ns per loop 

正如已经在注释中提到的那样,如果使用s.__contains__("XYZ")则会得到与s.startswith("XYZ")更类似的结果,因为它需要采用相同的路由:成员查找string对象,然后是一个函数调用。 这通常比较昂贵(当然,你不应该担心)。 另一方面,当你"XYZ" in s执行"XYZ" in s ,parsing器解释运算符,并且可以__contains__成员对__contains__访问(或者是它后面的实现,因为__contains__本身只是访问实现的一种方式) 。

你可以通过查看字节码来了解这个:

 >>> dis.dis('"XYZ" in s') 1 0 LOAD_CONST 0 ('XYZ') 3 LOAD_NAME 0 (s) 6 COMPARE_OP 6 (in) 9 RETURN_VALUE >>> dis.dis('s.__contains__("XYZ")') 1 0 LOAD_NAME 0 (s) 3 LOAD_ATTR 1 (__contains__) 6 LOAD_CONST 0 ('XYZ') 9 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 12 RETURN_VALUE 

因此,将s.__contains__("XYZ")s.startswith("XYZ")将产生更相似的结果,但是对于您的示例stringsstartswith仍然会变慢。

为了达到这个目的,你可以检查两者的执行情况。 包含实现的有趣之处在于它是静态types的,只是假定参数是一个unicode对象本身。 所以这是非常有效的。

然而startswith实现是一个“dynamic的”Python方法,它需要实现来实际parsing参数。 startswith还支持一个元组作为参数,这使得方法的整个启动速度变慢了一点(由我和我的评论缩短):

 static PyObject * unicode_startswith(PyObject *self, PyObject *args) { // argument parsing PyObject *subobj; PyObject *substring; Py_ssize_t start = 0; Py_ssize_t end = PY_SSIZE_T_MAX; int result; if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end)) return NULL; // tuple handling if (PyTuple_Check(subobj)) {} // unicode conversion substring = PyUnicode_FromObject(subobj); if (substring == NULL) {} // actual implementation result = tailmatch(self, substring, start, end, -1); Py_DECREF(substring); if (result == -1) return NULL; return PyBool_FromLong(result); } 

这可能是为什么由于其简单性而使contains很快的stringstartswith速度较慢的一个重要原因。

这可能是因为str.startswith()str.__contains__()更多str.__contains__() ,也是因为我相信str.__contains__完全在C str.__contains__运行,而str.startswith()必须与Pythontypes进行交互。 它的签名是str.startswith(prefix[, start[, end]]) ,其中前缀可以是要尝试的string元组。