Python 为什么字符串是';开始时比开始时慢?

Python 为什么字符串是';开始时比开始时慢?,python,python-2.7,cpython,python-internals,startswith,Python,Python 2.7,Cpython,Python Internals,Startswith,令人惊讶的是,我发现startswith比中的慢: 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 众所周知,中的操作需要搜索整个字符串,startswith只需要检查前几个字符,因此startsw

令人惊讶的是,我发现
startswith
中的
慢:

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
众所周知,
中的
操作需要搜索整个字符串,
startswith
只需要检查前几个字符,因此
startswith
应该更有效

s
足够大时,
startswith
速度更快:

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
似乎有一些开销,这使得字符串很小时速度会变慢

然后我试图找出
startswith
调用的开销是多少

首先,我使用了一个
f
变量来降低点操作的成本-如本文所述-这里我们可以看到
startswith
仍然较慢:

In [16]: f=s.startswith

In [17]: %timeit f("XYZ")
1000000 loops, best of 3: 270 ns per loop
此外,我还测试了空函数调用的成本:

In [18]: def func(a): pass

In [19]: %timeit func("XYZ")
10000000 loops, best of 3: 106 ns per loop
不考虑点操作和函数调用的成本,
startswith
的时间大约为(270-106)=164ns,但是
中的
操作只需要81.7ns。看来
startswith
还有一些开销,那是什么

按照poke和lvc的建议,将测试结果添加到
startswith
之间

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

这可能是因为
str.startswith()
str.startswith()做的更多,而且我相信
str.startswith()完全在C中运行,而
str.startswith()
必须与Python类型交互。它的签名是
str.startswith(prefix[,start[,end]])
,其中prefix可以是要尝试的字符串元组。

如注释中所述,如果使用
s.\uu包含
则得到的结果更类似于
s.startswith(“XYZ”)
因为它需要采用相同的路径:在字符串对象上查找成员,然后调用函数。这通常有点贵(当然不足以让你担心)。另一方面,当您在s
中执行
“XYZ”时,解析器解释运算符,并可以缩短成员对
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

您可以通过查看字节码了解这一点:

>>> 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.\uuu contains_uuuuz(“XYZ”)
s.startswith(“XYZ”)
进行比较将产生更相似的结果,但是对于示例字符串
s
startswith
仍然会更慢

要实现这一点,您可以检查这两种方法的实现。有趣的是,它是静态类型的,并且只假设参数本身是unicode对象。所以这是相当有效的

然而,这是一个“动态”Python方法,它要求实现实际解析参数
startswith
还支持将元组作为参数,这使得整个方法的启动速度稍慢:(由我缩短,并附上我的注释):

static PyObject*unicode_startswith(PyObject*self,PyObject*args)
{
//参数解析
PyObject*subobj;
PyObject*子字符串;
Py_ssize_t start=0;
Py_ssize_t end=Py_ssize_t_MAX;
int结果;
如果(!stringlib_parse_args_finds)(“startswith”、args、&subbj、&start、&end))
返回NULL;
//元组处理
if(PyTuple_Check(subobj)){}
//unicode转换
substring=PyUnicode_FromObject(subobj);
如果(子字符串==NULL){}
//实际执行
结果=尾匹配(self、substring、start、end,-1);
Py_DECREF(子串);
如果(结果==-1)
返回NULL;
从long返回PyBool_(结果);
}

这可能是
startswith
对于
包含的字符串来说速度较慢的一个重要原因,因为它的简单性。

要获得更相似的结果,可以执行
s.\uu包含
,因为这将采用与
s.startswith(“XYZ”)相同的路径。
(在
操作符中使用
将缩短成员访问)。然而,
startswith
对我来说仍然较慢。我认为剩余的性能差异是由于
\uuuuuu包含的
是用C完全键入的,而
startswith
执行实际的参数解析和填充(您还可以传递元组).您使用的是什么版本的Python?在3.4.3上,我得到了
s.startswith(“XYZ”)
s报告153ns和
s.。\uuuuuuuuuuuz(“XYZ”)
报告了169ns。正如@poke所说,在
中使用
将使用与方法调用完全不同的查找规则-它可以直接从C级的函数指针进行查找,而方法查找执行两次字典搜索,然后必须执行Python级的函数调用。分别计时这些事情可以为您提供一些ide有一点不同,但不一定是精确的。在你的数字上,减去这两项开销会使
开始的时间为负!我更进一步,检查了
%timeit“XYZ”==s[0:3]
,这给了我
10000000个循环,最好是每个循环3:94 ns
,而
%timeit“XYZ”在s
10000000个循环中,每个循环的最佳值为3:59.2ns
。使用python 3.4.3进行测试。(在我的例子中,切片似乎会产生“一些”开销,因为s[0:3]中的
%timeit“XYZ”
导致
10000000个循环,每个循环的最佳值为3:101 ns
)@LightnessRacesinOrbit虽然这是真的,
s
是字符串
“ABCD”
的重复,因此必须搜索整个字符串才能得出结论
“XYZ”
不包含在中。实现的链接为broken@mounaim很明显,Python的Mercurial服务器只关闭了几秒钟。它现在又开始工作了。“做更多”是模糊的,可能不是真的。高效地查找子字符串不是一个简单的问题。相比之下,查找前缀非常容易,而且非常简单