最初のテストケースは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
は、手作業で最適化されていることがわかります。