简体   繁体   English

为什么startwith比切片慢

[英]Why is startswith slower than slicing

Why is the implementation of startwith 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

Surprisingly, even including calculation for the length, slicing still appears significantly faster: 令人惊讶的是,即使包括计算长度,切片仍然显得更快:

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

Note: the first part of this behaviour is noted in Python for Data Analysis (Chapter 3), but no explanation for it is offered. 注意:此行为的第一部分在Python for Data Analysis (第3章)中有说明,但没有提供解释。

.

If helpful: here is the C code for startswith ; 如果有用: 这是startswith的C代码 ; and here is the output of dis.dis : 这是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 

Some of the performance difference can be explained by taking into account the time it takes the . 一些性能差异可以通过考虑它所花费的时间来解释. operator to do its thing: 操作员做它的事情:

>>> 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

Another portion of the difference can be explained by the fact that startswith is a function , and even no-op function calls take a bit of time: 另一部分差异可以通过以下事实来解释: startswith是一个函数 ,甚至无操作函数调用也需要一些时间:

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

This does not totally explain the difference, since the version using slicing and len calls a function and is still faster (compare to sw(y) above -- 267 ns): 这并不完全解释差异,因为使用切片和len的版本调用函数并且仍然更快(与上面的sw(y) - 267 ns):

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

My only guess here is that maybe Python optimizes lookup time for built-in functions, or that len calls are heavily optimized (which is probably true). 我唯一的猜测是,可能Python优化了内置函数的查找时间,或者len调优被大量优化(这可能是真的)。 It might be possible to test that with a custom len func. 有可能使用自定义len函数测试它。 Or possibly this is where the differences identified by LastCoder kick in. Note also larsmans ' results, which indicate that startswith is actually faster for longer strings. 或者这可能是LastCoder识别出的差异所在。请注意larsmans的结果,这表明对于较长的字符串, startswith实际上更快。 The whole line of reasoning above applies only to those cases where the overhead I'm talking about actually matters. 上面的整个推理仅适用于那些我正在谈论的开销实际上很重要的情况。

The comparison isn't fair since you're only measuring the case where startswith returns True . 比较是不公平的,因为您只测量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

Also, for much longer strings, startswith is a lot faster: 此外,对于更长的字符串, 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

This is still true when there's no match. 当没有匹配时,这仍然是正确的。

# 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

So, startswith is probably slower for short strings because it's optimized for long ones. 因此,对于短字符串, startswith可能更慢,因为它针对长字符串进行了优化。

(Trick to get random strings taken from this answer .) (欺骗从这个答案中获取随机字符串。)

startswith is more complex than slicing... startswith比切片更复杂......

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

This isn't a simple character compare loop for needle in beginning of haystack that's happening. 这不是干草堆开始时针头的简单字符比较循环。 We're looking at a for loop that is iterating through a vector/tuple (subobj) and calling another function ( _string_tailmatch ) on it. 我们正在寻找一个for循环,它遍历vector / tuple(subobj)并在其上调用另一个函数( _string_tailmatch )。 Multiple function calls have overhead with regards to the stack, argument sanity checks etc... 多个函数调用有关于堆栈,参数健全性检查等的开销。

startswith is a library function while the slicing appears to be built into the language. startswith是一个库函数,而切片似乎内置于该语言中。

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

To quote the docs , startswith does more you might think: 引用文档startswith做了更多你可能想到的:

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

Return True if string starts with the prefix, otherwise return False . 返回True如果字符串以前缀开头,否则返回False prefix can also be a tuple of prefixes to look for. 前缀也可以是要查找的前缀元组。 With optional start , test string beginning at that position. 使用可选的启动 ,测试字符串从该位置开始。 With optional end , stop comparing string at that position. 使用可选结束 ,停止比较该位置的字符串。

Calling a function is quite expensive. 调用函数非常昂贵。 I don't know, however, if this is the case as well for built-in functions written in C. 但是,我不知道是否也是用C编写的内置函数的情况。

Be aware, though, that slicing may involve a function call as well depending on the object which is used. 但请注意,切片可能还涉及函数调用,具体取决于所使用的对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM