12

ブラケットは のラッパーに過ぎないというのが私の理解でした__getitem__。これをベンチマークした方法は次のとおりです。

まず、準大規模辞書を生成しました。

items = {}
for i in range(1000000):
    items[i] = 1

次に、cProfile を使用して次の 3 つの関数をテストしました。

def get2(items):
    for k in items.iterkeys():
        items.get(k)

def magic3(items):
    for k in items.iterkeys():
        items.__getitem__(k)

def brackets1(items):
    for k in items.iterkeys():
        items[k]

結果は次のようになりました。

         1000004 function calls in 3.779 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    3.779    3.779 <string>:1(<module>)
        1    2.135    2.135    3.778    3.778 dict_get_items.py:15(get2)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000000    1.644    0.000    1.644    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'iterkeys' of 'dict' objects}


         1000004 function calls in 3.679 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    3.679    3.679 <string>:1(<module>)
        1    2.083    2.083    3.679    3.679 dict_get_items.py:19(magic3)
  1000000    1.596    0.000    1.596    0.000 {method '__getitem__' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'iterkeys' of 'dict' objects}


         4 function calls in 0.136 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.136    0.136 <string>:1(<module>)
        1    0.136    0.136    0.136    0.136 dict_get_items.py:11(brackets1)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'iterkeys' of 'dict' objects}

問題はベンチマークの方法にありますか? データが実際にアクセスされていることを確認するために、ブラケットアクセスを単純な「パス」に置き換えてみたところ、「パス」の方がはるかに高速に実行されていることがわかりました。これについての私の解釈は、データが実際にアクセスされていたというものでした。また、新しいリストに追加しようとしましたが、同様の結果が得られました。

4

1 に答える 1

9

まず、Not_a_Golferによって投稿された逆アセンブリ:

>>> d = {1:2}
>>> dis.dis(lambda: d[1])
  1           0 LOAD_GLOBAL              0 (d)
              3 LOAD_CONST               1 (1)
              6 BINARY_SUBSCR       
              7 RETURN_VALUE   

>>> dis.dis(lambda: d.get(1))
  1           0 LOAD_GLOBAL              0 (d)
              3 LOAD_ATTR                1 (get)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 RETURN_VALUE  

>>> dis.dis(lambda: d.__getitem__(1))
  1           0 LOAD_GLOBAL              0 (d)
              3 LOAD_ATTR                1 (__getitem__)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 RETURN_VALUE

さて、ベンチマークを正しくすることは、結果に何かを読み取るために明らかに重要であり、私はそこで多くを助けるのに十分なことを知りません。しかし、実際に違いがあると仮定すると(これは私には理にかなっています)、なぜあるのかについての私の推測は次のとおりです。

  1. dict.get単に「もっとやる」。キーが存在するかどうかを確認する必要があり、存在しない場合は2番目の引数(デフォルトはNone)を返す必要があります。これは、何らかの形の条件付きまたは例外キャッチがあることを意味します。したがって、これが、キーに関連付けられた値を取得するというより基本的な操作とは異なるタイミング特性を持つことにまったく驚いていません。

  2. Pythonには、「サブスクリプション」操作用の特定のバイトコードがあります(逆アセンブルで示されています)。を含む組み込み型dictは主にCで実装されており、それらの実装は必ずしも通常のPythonルールで機能するとは限りません(必要なのはインターフェイスのみであり、そこにも多くのコーナーケースがあります)。したがって、私の推測では、BINARY_SUBSCRオペコードの実装は、多かれ少なかれ、この操作をサポートする組み込み型の基礎となるC実装に直接行きます。__getitem__これらのタイプの場合、ブラケット構文がPythonレベルのメソッドを呼び出すのではなく、実際にはC実装をラップするPythonレベルのメソッドとして存在することを期待しています。

;を実装するカスタムクラスのインスタンスthing.__getitem__(key)に対してベンチマークを行うのは興味深いかもしれません。オペコードは、メソッドを検索して呼び出すのと同等の作業を行うために内部的にフォールバックする必要があるため、実際には反対の結果が表示される場合があります。thing[key]__getitem__BINARY_SUBSCR

于 2012-05-31T01:09:29.917 に答える