21

この素敵な答えに触発されて、

ベンチマークは次のとおりです。

import timeit

def test1():
    a = [1,2,3]
    a.insert(0,1)

def test2():
    a = [1,2,3]
    a[0:0]=[1]

print (timeit.timeit('test1()','from __main__ import test1'))
print (timeit.timeit('test2()','from __main__ import test2'))

私にとってtest2は、わずかに高速です(〜10%)。なぜそうなのですか?次の理由から、遅くなると思います。

  1. スライス割り当ては、任意の長さのイテラブルを受け入れることができなければならず、したがって、より一般的でなければなりません。
  2. スライスの割り当てでは、右側に新しいリストを作成して機能させる必要があります。

誰でもこれを理解するのを手伝ってもらえますか?

(OS-X 10.5.8 で python 2.7 を使用)

4

1 に答える 1

16

最初のテストケースはinsertリストのメソッドを呼び出す必要がありますaが、のすべての操作はtest2バイトコードで直接処理されます。CALL_FUNCTION以下の分解に注意してくださいtest1。Pythonでは、関数の呼び出しは適度にコストがかかります。実行時間の数パーセントの違いを説明するのに十分なコストです。

>>> import dis
>>> dis.dis(test1)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 BUILD_LIST               3
             12 STORE_FAST               0 (a)

  3          15 LOAD_FAST                0 (a)
             18 LOAD_ATTR                0 (insert)
             21 LOAD_CONST               4 (0)
             24 LOAD_CONST               1 (1)
             27 CALL_FUNCTION            2
             30 POP_TOP             
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE        
>>> dis.dis(test2)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 BUILD_LIST               3
             12 STORE_FAST               0 (a)

  3          15 LOAD_CONST               1 (1)
             18 BUILD_LIST               1
             21 LOAD_FAST                0 (a)
             24 LOAD_CONST               4 (0)
             27 LOAD_CONST               4 (0)
             30 STORE_SLICE+3       
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE        

悪い説明

これを最初に投稿しましたが、検討の結果、正しくないと思います。ここで説明する違いは、移動するデータが多い場合にのみ顕著な違いをもたらすはずですが、ここでのテストではそうではありません。そして、大量のデータがあっても、違いはほんの数パーセントです。

import timeit

def test1():
    a = range(10000000)
    a.insert(1,1)

def test2():
    a = range(10000000)
    a[1:1]=[1]

>>> timeit.timeit(test1, number=10)
6.008707046508789
>>> timeit.timeit(test2, number=10)
5.861173868179321

このメソッドlist.insertは、の関数によって実装されins1ますlistobject.c。リストの末尾のアイテム参照が1つずつコピーされていることがわかります。

for (i = n; --i >= where; )
    items[i+1] = items[i];

一方、スライスの割り当ては、次の関数list_ass_sliceを呼び出す関数によって実装されますmemmove

memmove(&item[ihigh+d], &item[ihigh],
        (k - ihigh)*sizeof(PyObject *));

したがって、あなたの質問に対する答えは、Cライブラリ関数memmoveが単純なループよりも最適化されているということだと思います。:のglibc実装については、ここをmemmove参照してください。そこから呼び出されると、list_ass_slice最終的に_wordcopy_bwd_alignedは、手作業で最適化されていることがわかります。

于 2012-09-21T20:42:05.373 に答える