【发布时间】:2015-11-02 05:43:25
【问题描述】:
令人惊讶的是,我发现 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 变量来降低点运算的成本——正如这个answer 中提到的——这里我们可以看到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对我来说仍然较慢。 -
我认为性能差异的其余部分是由于
__contains__被完全键入在 C 中,而startswith执行实际参数解析和其他东西(你也可以通过一个元组)。 -
您使用的是什么版本的 Python?在 3.4.3 上,我得到
s.startswith("XYZ")报告 153ns,s.__contains__("XYZ")报告 169ns。正如@poke 所说,使用in将使用与方法调用完全不同的查找规则——它可以直接从C 级别的函数指针中查找,而方法查找执行两个字典搜索和then i> 必须进行 Python 级别的函数调用。分别计时这些事情可以让您一些了解差异,但不一定准确。在你的数字上,减去这两项开销就可以得到startswithnegative! -
我更进一步检查了
%timeit "XYZ" == s[0:3],它给了我10000000 loops, best of 3: 94 ns per loop,而%timeit "XYZ" in s10000000 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) -
@LightnessRacesinOrbit 虽然这是真的,但
s是字符串"ABCD"的重复,因此必须搜索整个字符串才能得出"XYZ"不包含在其中的结论.
标签: python python-2.7 cpython python-internals startswith