簡體   English   中英

為什么startwith比切片慢

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM