为什么开始慢于切片

为什么startwith的执行慢于切片?

 In [1]: x = 'foobar' In [2]: y = 'foo' In [3]: %timeit x.startswith(y) 1000000 loops, best of 3: 321 ns per loop In [4]: %timeit x[:3] == y 10000000 loops, best of 3: 164 ns per loop 

令人惊讶的是,即使包括计算长度,切片仍然显得更快:

 In [5]: %timeit x[:len(y)] == y 1000000 loops, best of 3: 251 ns per loop 

注意:这个行为的第一部分在Python中用于数据分析 (第3章),但没有提供任何解释。

如果有帮助的话: 这里是startswith的C代码 ; 这里是dis.dis的输出:

 In [6]: import dis In [7]: dis_it = lambda x: dis.dis(compile(x, '<none>', 'eval')) In [8]: dis_it('x[:3]==y') 1 0 LOAD_NAME 0 (x) 3 LOAD_CONST 0 (3) 6 SLICE+2 7 LOAD_NAME 1 (y) 10 COMPARE_OP 2 (==) 13 RETURN_VALUE In [9]: dis_it('x.startswith(y)') 1 0 LOAD_NAME 0 (x) 3 LOAD_ATTR 1 (startswith) 6 LOAD_NAME 2 (y) 9 CALL_FUNCTION 1 12 RETURN_VALUE 

一些性能差异可以通过考虑所花费的时间来解释. 运营商做它的事情:

 >>> x = 'foobar' >>> y = 'foo' >>> sw = x.startswith >>> %timeit x.startswith(y) 1000000 loops, best of 3: 316 ns per loop >>> %timeit sw(y) 1000000 loops, best of 3: 267 ns per loop >>> %timeit x[:3] == y 10000000 loops, best of 3: 151 ns per loop 

另一部分区别可以通过以下事实来解释: startswith是一个函数 ,甚至no-op函数调用需要一点时间:

 >>> def f(): ... pass ... >>> %timeit f() 10000000 loops, best of 3: 105 ns per loop 

这并不能完全解释这种差异,因为使用slicing和len的版本调用一个函数并且仍然更快(与上面的sw(y)比较 – 267 ns):

 >>> %timeit x[:len(y)] == y 1000000 loops, best of 3: 213 ns per loop 

我唯一的猜测是Python可能会优化内置函数的查询时间,或者len调用会被大量优化(这可能是真的)。 有可能用自定义的len func来testing。 或者可能是由LastCoder识别的差异所在。还要注意larsmans的结果,这表明对于更长的string, startswith实际上更快。 以上全部的推理只适用于那些我所说的开销实际上很重要的情况。

比较是不公平的,因为你只测量了startswith返回True的情况。

 >>> x = 'foobar' >>> y = 'fool' >>> %timeit x.startswith(y) 1000000 loops, best of 3: 221 ns per loop >>> %timeit x[:3] == y # note: length mismatch 10000000 loops, best of 3: 122 ns per loop >>> %timeit x[:4] == y 10000000 loops, best of 3: 158 ns per loop >>> %timeit x[:len(y)] == y 1000000 loops, best of 3: 210 ns per loop >>> sw = x.startswith >>> %timeit sw(y) 10000000 loops, best of 3: 176 ns per loop 

而且,对于更长的string, startswith要快很多:

 >>> import random >>> import string >>> x = '%030x' % random.randrange(256**10000) >>> len(x) 20000 >>> y = r[:4000] >>> %timeit x.startswith(y) 1000000 loops, best of 3: 211 ns per loop >>> %timeit x[:len(y)] == y 1000000 loops, best of 3: 469 ns per loop >>> sw = x.startswith >>> %timeit sw(y) 10000000 loops, best of 3: 168 ns per loop 

如果没有匹配,这仍然是正确的。

 # change last character of y >>> y = y[:-1] + chr((ord(y[-1]) + 1) % 256) >>> %timeit x.startswith(y) 1000000 loops, best of 3: 210 ns per loop >>> %timeit x[:len(y)] == y 1000000 loops, best of 3: 470 ns per loop >>> %timeit sw(y) 10000000 loops, best of 3: 168 ns per loop # change first character of y >>> y = chr((ord(y[0]) + 1) % 256) + y[1:] >>> %timeit x.startswith(y) 1000000 loops, best of 3: 210 ns per loop >>> %timeit x[:len(y)] == y 1000000 loops, best of 3: 442 ns per loop >>> %timeit sw(y) 10000000 loops, best of 3: 168 ns per loop 

所以,对于短string, startswith可能会比较慢,因为它对长字符进行了优化。

(哄骗从这个答案中得到随机的string。)

startswith比切片更复杂…

 2924 result = _string_tailmatch(self, 2925 PyTuple_GET_ITEM(subobj, i), 2926 start, end, -1); 

这不是一个简单的字符比较在干草堆开始发生的针比较循环。 我们正在寻找一个for循环,通过vector / tuple(subobj)迭代并调用另一个函数( _string_tailmatch )。 多个函数调用在堆栈,参数完整性检查等方面都有开销。

startswith是一个库函数,而切片看起来是内置在语言中的。

 2919 if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end)) 2920 return NULL; 

引用文档 , startswith你可能会想:

str.startswith(prefix[, start[, end]])

如果string以前缀开头,则返回False ,否则返回False 。 前缀也可以是要查找的前缀元组。 使用可选启动 ,testingstring从该位置开始。 用可选的结束 ,停止比较在那个位置的string。

调用一个函数是相当昂贵的。 但是,我不知道这是否也适用于C语言编写的内置函数。

但请注意,切片也可能涉及函数调用,具体取决于所使用的对象。