为什么startwith比切片慢

And*_*den 47 python startswith

为什么执行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
Run Code Online (Sandbox Code Playgroud)

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

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

注意:此行为的第一部分在Python for Data Analysis(第3章)中有说明,但没有提供解释.

.

如果有用:这是C代码startswith ; 这是输出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 
Run Code Online (Sandbox Code Playgroud)

sen*_*rle 37

可以通过考虑.操作员执行其操作所花费的时间来解释一些性能差异:

>>> 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
Run Code Online (Sandbox Code Playgroud)

差的另一部分可以通过以下事实来解释startswith是一个函数,和甚至无操作函数调用需要一点时间:

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

这并没有完全解释差异,因为使用切片和len调用函数的版本仍然更快(与sw(y)上面相比 - 267 ns):

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

我唯一的猜测是,Python可能会优化内置函数的查找时间,或者len调用被大量优化(这可能是真的).可以使用自定义len函数测试它.或者,也许这就是查明的差异LastCoder在踢.还要注意larsmans '的结果,这表明startswith实际上是更长的字符串更快.上面的整个推理仅适用于那些我正在谈论的开销实际上很重要的情况.


Fre*_*Foo 27

这种比较是不公平的,因为你只是在衡量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
Run Code Online (Sandbox Code Playgroud)

此外,对于更长的字符串,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
Run Code Online (Sandbox Code Playgroud)

当没有匹配时,这仍然是正确的.

# 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
Run Code Online (Sandbox Code Playgroud)

因此,startswith对于短字符串来说可能更慢,因为它针对长字符串进行了优化.

(欺骗从这个答案中获取随机字符串.)


Lou*_*cci 9

startswith 比切片更复杂......

2924 result = _string_tailmatch(self,
2925 PyTuple_GET_ITEM(subobj, i),
2926 start, end, -1);
Run Code Online (Sandbox Code Playgroud)

这不是干草堆开始时针头的简单字符比较循环.我们正在寻找一个for循环,它遍历vector/tuple(subobj)并_string_tailmatch在其上调用另一个函数().多个函数调用有关于堆栈,参数健全性检查等的开销.

startswith 是一个库函数,而切片似乎是内置于语言中.

2919 if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end))
2920 return NULL;
Run Code Online (Sandbox Code Playgroud)

  • _"StartsWith可以处理矢量."_ - 不知道你的意思. (3认同)

Eri*_*ric 8

引用文档,startswith您可能会想到更多:

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

返回True如果字符串以前缀开头,否则返回False.前缀也可以是要查找的前缀元组.使用可选的 启动,测试字符串从该位置开始.使用可选结束,停止比较该位置的字符串.