web-dev-qa-db-ja.com

スライスよりも開始が遅いのはなぜですか

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 
50
Andy Hayden

パフォーマンスの違いのいくつかは、_._演算子がその処理を実行するのにかかる時間を考慮することで説明できます。

_>>> 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が実際には長い文字列の方が速いことを示しています。行全体上記の推論は、私が話しているオーバーヘッドが実際に重要である場合にのみ適用されます。

36
senderle

startswithTrueを返す場合のみを測定しているため、比較は公平ではありません。

>>> 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は長い文字列用に最適化されているため、短い文字列の場合はおそらく遅くなります。

(ランダムな文字列を取得するためのトリック この回答 。)

26
Fred Foo

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;
9
Louis Ricci

引用すると docsstartswithはあなたが思うかもしれないより多くのことをします:

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

文字列がプレフィックスで始まる場合はTrueを返し、そうでない場合はFalseを返します。プレフィックスは、検索するプレフィックスのタプルにすることもできます。オプションのstartを使用して、その位置から始まる文字列をテストします。オプションのendを使用して、その位置で文字列の比較を停止します。

8
Eric

関数の呼び出しは非常にコストがかかります。ただし、これがCで記述された組み込み関数にも当てはまるかどうかはわかりません。

ただし、使用するオブジェクトによっては、スライスに関数呼び出しが含まれる場合があることに注意してください。

0
glglgl