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服务器只关闭了几秒钟。它现在又开始工作了。“做更多”是模糊的,可能不是真的。高效地查找子字符串不是一个简单的问题。相比之下,查找前缀非常容易,而且非常简单