为什么startswith比慢切片(Why is startswith slower than sl

2019-07-03 15:56发布

为什么执行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章),但它没有解释提供。

如果有帮助的: 这里是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 

Answer 1:

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

>>> 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纳秒):

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

我在这里只猜测是,也许Python的优化查找时间为内置函数,或len呼叫高度优化的(这可能是真的)。 这可能是可以测试与自定义len FUNC。 或者,也许这就是查明的差异LastCoder踢。还要注意larsmans '的结果,这表明startswith实际上更快更长的字符串。 上述推理的整条生产线只适用于在那里我实际上是在谈论开销事项的情况下。



Answer 2:

比较是不公平的,因为你只测量其中的情况下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可能是短字符串,因为它是长句子优化慢。

(绝招摆脱采取随机字符串这个答案 。)



Answer 3:

startswith比切片更复杂...

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

这不是一个简单的人物比较环路在草垛发生的事情的开端针。 我们正在寻找一个for循环,通过矢量/元组(subobj)迭代并调用另一个函数( _string_tailmatch就可以了)。 多个函数调用的开销都与问候到堆栈,参数完整性检查等..

startswith是一个库函数,而切片似乎是语言内置的。

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


Answer 4:

引述的文档 , startswith也越多,你可能会想:

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

返回True如果字符串以前缀开头,否则返回False 。 前缀也可以前缀查找的元组。 通过可选的启动 ,测试字符串开头的那个位置。 通过可选的结束 ,停在那个位置比较字符串。



Answer 5:

调用一个函数是相当昂贵的。 我不知道,但是,如果是这种情况,以及对C写的内置函数

要知道,虽然,切片可能涉及的函数调用,以及根据所使用的对象。



文章来源: Why is startswith slower than slicing