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