令人惊讶的是,我发现startswith
比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
操作需要搜索整个字符串,而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,但 in
操作仅需要81.7ns。看起来 startswith
仍然存在一些开销,那是什么呢?按照 poke 和 lvc 建议添加
startswith
和 __contains__
之间的测试结果: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")
相同的路线(使用in
运算符将快捷成员访问)。然而,对我来说,startswith
仍然较慢。 - poke__contains__
在C中完全类型化,而startswith
则执行实际的参数解析和其他操作(您还可以传递元组)。 - pokes.startswith("XYZ")
报告153ns和s.__contains__("XYZ")
报告169ns。正如@poke所说,使用in
将使用完全不同的查找规则,它可以直接从C级别的函数指针进行查找,而方法查找则需要进行两个字典搜索,然后必须进行Python级别的函数调用。分别计时这些操作可以给你一些关于差异的想法,但不一定是准确的。根据你的数据,减去这两个开销会使startswith
的时间变为负数! - lvc%timeit "XYZ" == s[0:3]
,结果为10000000 loops, best of 3: 94 ns per loop
,而%timeit "XYZ" in s
的结果为10000000 loops, best of 3: 59.2 ns per loop
。这是使用Python 3.4.3测试的。似乎在我的情况下,切片会带来一些额外的开销,因为%timeit "XYZ" in s[0:3]
的结果为10000000 loops, best of 3: 101 ns per loop
。 - Marandils
是字符串"ABCD"
的重复,因此必须搜索整个字符串才能得出结论,即"XYZ"
不包含在内。 - poke