[英]Why is startswith slower than slicing
为什么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 for Data Analysis (第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
是一个函数 ,甚至无操作函数调用也需要一些时间:
>>> def f():
... pass
...
>>> %timeit f()
10000000 loops, best of 3: 105 ns per loop
这并不完全解释差异,因为使用切片和len
的版本调用函数并且仍然更快(与上面的sw(y)
- 267 ns):
>>> %timeit x[:len(y)] == y
1000000 loops, best of 3: 213 ns per loop
我唯一的猜测是,可能Python优化了内置函数的查找时间,或者len
调优被大量优化(这可能是真的)。 有可能使用自定义len
函数测试它。 或者这可能是LastCoder识别出的差异所在。请注意larsmans的结果,这表明对于较长的字符串, 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
此外,对于更长的字符串, 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
因此,对于短字符串, startswith
可能更慢,因为它针对长字符串进行了优化。
(欺骗从这个答案中获取随机字符串。)
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]])
返回
True
如果字符串以前缀开头,否则返回False
。 前缀也可以是要查找的前缀元组。 使用可选的启动 ,测试字符串从该位置开始。 使用可选结束 ,停止比较该位置的字符串。
调用函数非常昂贵。 但是,我不知道是否也是用C编写的内置函数的情况。
但请注意,切片可能还涉及函数调用,具体取决于所使用的对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.