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
が関数であり、no-op関数呼び出しでさえ少し時間がかかるという事実によって説明できます。
_>>> def f():
... pass
...
>>> %timeit f()
10000000 loops, best of 3: 105 ns per loop
_
スライスとlen
を使用するバージョンは関数を呼び出し、それでも高速であるため、これは完全に違いを説明しません(sw(y)
と比較してください)上記-267ns):
_>>> %timeit x[:len(y)] == y
1000000 loops, best of 3: 213 ns per loop
_
ここでの私の唯一の推測は、おそらくPythonが組み込み関数のルックアップ時間を最適化するか、len
呼び出しが大幅に最適化される(おそらく本当です)ということです。カスタムでテストできる可能性があります。 len
func。または、おそらくこれは、 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);
これは、起こっている干し草の山の始まりにある針の単純な文字比較ループではありません。ベクトル/タプル(subobj)を反復処理し、その上で別の関数(_string_tailmatch
)を呼び出すforループを調べています。複数の関数呼び出しには、スタック、引数のサニティチェックなどに関してオーバーヘッドがあります。
startswith
はライブラリ関数ですが、スライスは言語に組み込まれているように見えます。
2919 if (!stringlib_parse_args_finds("startswith", args, &subobj, &start, &end))
2920 return NULL;
引用すると docs 、startswith
はあなたが思うかもしれないより多くのことをします:
str.startswith(prefix[, start[, end]])
文字列がプレフィックスで始まる場合は
True
を返し、そうでない場合はFalse
を返します。プレフィックスは、検索するプレフィックスのタプルにすることもできます。オプションのstartを使用して、その位置から始まる文字列をテストします。オプションのendを使用して、その位置で文字列の比較を停止します。
関数の呼び出しは非常にコストがかかります。ただし、これがCで記述された組み込み関数にも当てはまるかどうかはわかりません。
ただし、使用するオブジェクトによっては、スライスに関数呼び出しが含まれる場合があることに注意してください。