1. ホーム
  2. python

[解決済み] なぜ[]はlist()よりも速いのですか?

2022-03-20 10:14:32

質問

の処理速度を比較しました。 []list() を発見して驚きました。 [] 実行 3倍以上速い よりも list() . 同じテストを {}dict() と、ほぼ同じ結果になりました。 []{} は約0.128秒/100万サイクルであるのに対し list()dict() は、それぞれ約0.428秒/100万サイクルかかりました。

これはなぜでしょうか?はたして []{} (そしておそらく ()'' も)は直ちに空のストック・リテラルのコピーを返すのに対し、明示的に名前を付けた対応するもの( list() , dict() , tuple() , str() ) が、実際に要素を持っているかどうかにかかわらず、オブジェクトを作成するために完全に行くのですか?

この2つの方法がどう違うのか、まったくわからないのですが、ぜひ調べてみたいです。 ドキュメントやSOで答えを見つけることができず、空のブラケットを検索すると、予想以上に問題があることがわかりました。

を呼び出して、タイミング結果を得ました。 timeit.timeit("[]")timeit.timeit("list()") および timeit.timeit("{}")timeit.timeit("dict()") で、それぞれリストと辞書を比較します。Python 2.7.9を使用しています。

最近、"を発見しました。 なぜ if True は if 1 よりも遅いのですか? のパフォーマンスを比較したものです。 if True から if 1 というような、リテラル対グローバルのシナリオに触れているようで、こちらも検討する価値がありそうです。

どのように解決するのか?

なぜなら []{} リテラルシンタックス . Pythonはリストや辞書オブジェクトを作成するためだけにバイトコードを作成することができます。

>>> import dis
>>> dis.dis(compile('[]', '', 'eval'))
  1           0 BUILD_LIST               0
              3 RETURN_VALUE        
>>> dis.dis(compile('{}', '', 'eval'))
  1           0 BUILD_MAP                0
              3 RETURN_VALUE        

list()dict() は別のオブジェクトです。それらの名前は解決される必要があり、スタックは引数をプッシュするために関与しなければならず、フレームは後で取り出すために保存されなければならず、そして呼び出しが行われなければなりません。その分、時間がかかります。

空の場合、最低でも LOAD_NAME (これはグローバル名前空間だけでなく builtins モジュール ) に続いて CALL_FUNCTION これは、現在のフレームを保持する必要があります。

>>> dis.dis(compile('list()', '', 'eval'))
  1           0 LOAD_NAME                0 (list)
              3 CALL_FUNCTION            0
              6 RETURN_VALUE        
>>> dis.dis(compile('dict()', '', 'eval'))
  1           0 LOAD_NAME                0 (dict)
              3 CALL_FUNCTION            0
              6 RETURN_VALUE        

名前検索は、別途 timeit :

>>> import timeit
>>> timeit.timeit('list', number=10**7)
0.30749011039733887
>>> timeit.timeit('dict', number=10**7)
0.4215109348297119

そこでの時間のズレは、おそらく辞書のハッシュの衝突です。これらのオブジェクトを呼び出す時間からその時間を差し引き、その結果をリテラルを使用した場合の時間と比較してみてください。

>>> timeit.timeit('[]', number=10**7)
0.30478692054748535
>>> timeit.timeit('{}', number=10**7)
0.31482696533203125
>>> timeit.timeit('list()', number=10**7)
0.9991960525512695
>>> timeit.timeit('dict()', number=10**7)
1.0200958251953125

そのため、オブジェクトを呼び出すには、さらに 1.00 - 0.31 - 0.30 == 0.39 1,000万回の呼び出しで数秒。

グローバル名をローカル名としてエイリアスすることで、グローバル検索コストを回避することができます ( timeit を設定すると、名前にバインドされているものはすべてローカルになります)。

>>> timeit.timeit('_list', '_list = list', number=10**7)
0.1866450309753418
>>> timeit.timeit('_dict', '_dict = dict', number=10**7)
0.19016098976135254
>>> timeit.timeit('_list()', '_list = list', number=10**7)
0.841480016708374
>>> timeit.timeit('_dict()', '_dict = dict', number=10**7)
0.7233691215515137

しかし、それを克服することはできない CALL_FUNCTION のコストがかかります。