53

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章)に記載されていますが、説明はありません。

役立つ場合:ここに;のCコードがあります。startswithそしてここにの出力があります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 
4

5 に答える 5

41

パフォーマンスの違いの一部.は、オペレーターがその作業を行うのにかかる時間を考慮に入れることで説明できます。

>>> 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違いのもう 1 つの部分は、それがfunctionであるという事実によって説明できます。また、no-op 関数呼び出しでさえ、少し時間がかかります。

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

スライスを使用して関数を呼び出すバージョンはまだ高速であるため、これは違いを完全に説明するものではありません(上記と比較して -- 267 ns):lensw(y)

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

ここでの私の唯一の推測は、Python が組み込み関数のルックアップ時間を最適化するか、len呼び出しが大幅に最適化されていることです (これはおそらく本当です)。カスタム関数でそれをテストすることは可能かもしれませんlenあるいは、 LastCoderによって識別された違いがここで発生する可能性もあります。larsmansの結果にも注意してください。これはstartswith、長い文字列の方が実際には高速であることを示しています。上記の推論の全行は、私が話しているオーバーヘッドが実際に重要な場合にのみ適用されます。

于 2012-11-07T13:48:59.767 に答える
34

startswithreturnのケースのみを測定しているため、比較は公平ではありません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長い文字列用に最適化されているため、短い文字列ではおそらく遅くなります。

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

于 2012-11-07T14:03:18.493 に答える
9

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;
于 2012-11-07T13:43:59.370 に答える
8

docs を引用するには、次のstartswithように考えるかもしれません。

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

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

于 2012-11-07T14:44:45.907 に答える
0

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

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

于 2012-11-07T13:46:00.370 に答える