タプルはPythonのリストよりも効率的ですか?


225

要素のインスタンス化と取得に関して、タプルとリストの間にパフォーマンスの違いはありますか?



2
あなたがばらつくの間で使用されるメモリに興味があるなら、私が作ったこのプロットを参照して入力します。stackoverflow.com/a/30008338/2087463
tmthydvnprt

回答:


172

disモジュールが機能するためにバイトコードを逆アセンブルし、タプルとリストの違いを確認するのに便利です。

この場合、要素にアクセスすると同じコードが生成されますが、タプルの割り当てはリストの割り当てよりもはるかに高速です。

>>> def a():
...     x=[1,2,3,4,5]
...     y=x[2]
...
>>> def b():
...     x=(1,2,3,4,5)
...     y=x[2]
...
>>> import dis
>>> dis.dis(a)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 LOAD_CONST               4 (4)
             12 LOAD_CONST               5 (5)
             15 BUILD_LIST               5
             18 STORE_FAST               0 (x)

  3          21 LOAD_FAST                0 (x)
             24 LOAD_CONST               2 (2)
             27 BINARY_SUBSCR
             28 STORE_FAST               1 (y)
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE
>>> dis.dis(b)
  2           0 LOAD_CONST               6 ((1, 2, 3, 4, 5))
              3 STORE_FAST               0 (x)

  3           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 (2)
             12 BINARY_SUBSCR
             13 STORE_FAST               1 (y)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

66
エラー、同じバイトコードが生成されただけでは、同じ操作がC(したがってCPU)レベルで行われるわけではありません。ひどく遅い何かListLikeをする__getitem__を使ってクラスを作成してから、逆アセンブルしてみてくださいx = ListLike((1, 2, 3, 4, 5)); y = x[2]。バイトコードはリストの例よりも上のタプルの例に似ていますが、パフォーマンスが同じになることを本当に信じていますか?
mzz 2010年

2
一部のタイプは他のタイプより効率的であると言っているようです。これは理にかなっていますが、リストとタプルの生成のオーバーヘッドは、関連するデータ型と直交しているように見えますが、同じデータ型のリストとタプルであることに注意してください。
マークハリソン

11
コードの行数と同様に、バイトコードの数は、実行速度(したがって、効率とパフォーマンス)とはほとんど関係がありません。
martineau

18
opのカウントから何かを結論付けることができるという提案は誤っていますが、これは主な違いを示しています。定数タプルはそのままバイトコードに格納され、使用時に参照されるだけですが、リストは実行時に構築する必要があります。
poolie 2013年

6
この答えは、Pythonがタプル定数を認識することを示しています。知っておくと便利です。しかし、変数値からタプルまたはリストを作成しようとするとどうなりますか?
トム

211

一般に、タプルの方がわずかに速いと予想される場合があります。ただし、特定のケースを確実にテストする必要があります(違いがプログラムのパフォーマンスに影響を与える可能性がある場合-「時期尚早の最適化がすべての悪の根源である」ことを忘れないでください)。

Pythonはこれを非常に簡単にします。timeitはあなたの友達です。

$ python -m timeit "x=(1,2,3,4,5,6,7,8)"
10000000 loops, best of 3: 0.0388 usec per loop

$ python -m timeit "x=[1,2,3,4,5,6,7,8]"
1000000 loops, best of 3: 0.363 usec per loop

そして...

$ python -m timeit -s "x=(1,2,3,4,5,6,7,8)" "y=x[3]"
10000000 loops, best of 3: 0.0938 usec per loop

$ python -m timeit -s "x=[1,2,3,4,5,6,7,8]" "y=x[3]"
10000000 loops, best of 3: 0.0649 usec per loop

したがって、この場合、インスタンス化はタプルの方がほぼ1桁速くなりますが、アイテムアクセスは実際にはリストの方がいくらか速くなります。したがって、いくつかのタプルを作成し、それらに何度もアクセスする場合は、代わりにリストを使用する方が実際には高速になる可能性があります。

もちろん、アイテムを変更したい場合は、1つのアイテムを変更するために新しいタプル全体を作成する必要があるため、リストは間違いなく高速になります(タプルは不変であるため)。


3
テストに使用したpythonのバージョンは何ですか?
マットジョイナー

2
別の興味深いテストがあります- python -m timeit "x=tuple(xrange(999999))"python -m timeit "x=list(xrange(999999))"。予想されるように、タプルの具体化にはリストよりも少し時間がかかります。
Hamish Grubijan、2012年

3
タプルへのアクセスがリストへのアクセスよりも遅いのは奇妙に思えます。ただし、Windows 7 PCのPython 2.7でそれを試しても、違いはわずか10%なので、重要ではありません。
ToolmakerSteve

51
FWIW、リストアクセスはPython 2のタプルアクセスよりも高速ですが、それはPython / ceval.cのBINARY_SUBSCRにリストの特殊なケースがあるためです。Python 3では、その最適化はなくなり、タプルへのアクセスはリストへのアクセスよりもわずかに速くなります。
レイモンドヘッティンガー、2014年

3
@yoopoo、最初のテストでは100万回リストが作成されますが、2番目のテストではリストが1回作成され、100万回アクセスされます。-s "SETUP_CODE"実際の時限コードの前に実行されます。
leewz

203

概要

タプルはリストよりもパフォーマンスが高い傾向があります、ほとんどすべてのカテゴリのます。

1)タプルは常に折りたたむことができます

2)タプルはコピーする代わりに再利用できます。

3)タプルはコンパクトで、過剰に割り当てられません。

4)タプルはその要素を直接参照します。

タプルは常に折りたたむことができます

定数のタプルは、PythonのピープホールオプティマイザーまたはASTオプティマイザーによって事前計算できます。一方、リストはゼロから作成されます。

    >>> from dis import dis

    >>> dis(compile("(10, 'abc')", '', 'eval'))
      1           0 LOAD_CONST               2 ((10, 'abc'))
                  3 RETURN_VALUE   

    >>> dis(compile("[10, 'abc']", '', 'eval'))
      1           0 LOAD_CONST               0 (10)
                  3 LOAD_CONST               1 ('abc')
                  6 BUILD_LIST               2
                  9 RETURN_VALUE 

タプルをコピーする必要はありません

実行tuple(some_tuple)するとすぐに戻ります。タプルは不変なので、コピーする必要はありません。

>>> a = (10, 20, 30)
>>> b = tuple(a)
>>> a is b
True

対照的に、list(some_list)すべてのデータを新しいリストにコピーする必要があります。

>>> a = [10, 20, 30]
>>> b = list(a)
>>> a is b
False

タプルは過剰に割り当てられません

タプルのサイズは固定されているため、append()操作を効率的にするために過剰に割り当てる必要があるリストよりもコンパクトに格納できます。

これはタプルに素晴らしいスペースの利点を与えます:

>>> import sys
>>> sys.getsizeof(tuple(iter(range(10))))
128
>>> sys.getsizeof(list(iter(range(10))))
200

以下は、リストが何をしているのかを説明するObjects / listobject.cからのコメントです。

/* This over-allocates proportional to the list size, making room
 * for additional growth.  The over-allocation is mild, but is
 * enough to give linear-time amortized behavior over a long
 * sequence of appends() in the presence of a poorly-performing
 * system realloc().
 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
 * Note: new_allocated won't overflow because the largest possible value
 *       is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
 */

タプルはその要素を直接参照します

オブジェクトへの参照は、タプルオブジェクトに直接組み込まれます。対照的に、リストには、ポインターの外部配列への間接的な追加レイヤーがあります。

これにより、タプルにインデックス付きルックアップとアンパックの速度が少し向上します。

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'a[1]'
10000000 loops, best of 3: 0.0304 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'a[1]'
10000000 loops, best of 3: 0.0309 usec per loop

$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'x, y, z = a'
10000000 loops, best of 3: 0.0249 usec per loop
$ python3.6 -m timeit -s 'a = [10, 20, 30]' 'x, y, z = a'
10000000 loops, best of 3: 0.0251 usec per loop

ここでタプルがどのように(10, 20)格納されます。

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject *ob_item[2];     /* store a pointer to 10 and a pointer to 20 */
    } PyTupleObject;

ここにリストがどのように[10, 20]格納されます。

    PyObject arr[2];              /* store a pointer to 10 and a pointer to 20 */

    typedef struct {
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
        Py_ssize_t ob_size;
        PyObject **ob_item = arr; /* store a pointer to the two-pointer array */
        Py_ssize_t allocated;
    } PyListObject;

タプルオブジェクトには2つのデータポインターが直接組み込まれていますが、リストオブジェクトには2つのデータポインターを保持する外部配列への間接的な追加レイヤーがあります。


19
最後に、誰かがC構造体を配置します!
osman 2015

1
Internally, tuples are stored a little more efficiently than lists, and also tuples can be accessed slightly faster. では、dFの回答の結果をどのように説明できますか?
DRz 2016

5
〜100の要素リストの〜50kのリストを処理する場合、この構造をタプルに移動すると、複数のルックアップでルックアップ時間が数桁減少しました。これは、デモンストレーションの2番目の間接層が削除されたため、タプルの使用を開始すると、タプルのキャッシュの局所性が大きくなったためと考えられます。
horta

tuple(some_tuple)がハッシュ可能である場合、つまりその内容が再帰的に不変でハッシュ可能であるsome_tuple場合にのみ自分自身を返しますsome_tuple。それ以外の場合tuple(some_tuple)は、新しいタプルを返します。たとえば、some_tuple変更可能なアイテムが含まれている場合です。
Luciano Ramalho

タプルが常に高速であるとは限りません。```t =()for i in range(1,100):t + = il = [] for i in range(1,1000):a.append(i) `` `を検討してくださいより速い
メルビルジェームズ

32

タプルは不変であるため、メモリ効率が向上します。リストは、効率を上げるために、定数reallocs なしの追加を可能にするためにメモリを割り当てます。したがって、コード内の値の定数シーケンスを反復処理する場合(たとえば、for direction in 'up', 'right', 'down', 'left'::)、タプルはコンパイル時に事前計算されるため、タプルが推奨されます。

アクセス速度は同じである必要があります(どちらも連続した配列としてメモリに格納されます)。

しかし、変更可能なデータを処理alist.append(item)するatuple+= (item,)場合には、がはるかに優先されます。タプルはフィールド名のないレコードとして扱われることを意図していることに注意してください。


1
Pythonのコンパイル時間とは何ですか?
balki

1
@balki:Pythonソースがバイトコードにコンパイルされる時間(バイトコードは.py [co]ファイルとして保存される可能性があります)。
tzot

できれば引用は素晴らしいでしょう。
Grijesh Chauhan 14

9

arrayリストまたはタプルのすべてのアイテムが同じCタイプである場合は、標準ライブラリのモジュールも検討する必要があります。メモリ使用量が少なくなり、処理速度が向上します。


15
必要なメモリは少なくなりますが、アクセス時間はおそらく高速ではなく少し遅くなります。要素にアクセスするには、パックされた値を実際の整数にボックス化する必要があります。これにより、プロセスが遅くなります。
ブライアン、

5

ここに、もう1つの小さなベンチマークがあります。

In [11]: %timeit list(range(100))
749 ns ± 2.41 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [12]: %timeit tuple(range(100))
781 ns ± 3.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [1]: %timeit list(range(1_000))
13.5 µs ± 466 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [2]: %timeit tuple(range(1_000))
12.4 µs ± 182 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %timeit list(range(10_000))
182 µs ± 810 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [8]: %timeit tuple(range(10_000))
188 µs ± 2.38 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [3]: %timeit list(range(1_00_000))
2.76 ms ± 30.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit tuple(range(1_00_000))
2.74 ms ± 31.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [10]: %timeit list(range(10_00_000))
28.1 ms ± 266 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit tuple(range(10_00_000))
28.5 ms ± 447 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

これらを平均してみましょう:

In [3]: l = np.array([749 * 10 ** -9, 13.5 * 10 ** -6, 182 * 10 ** -6, 2.76 * 10 ** -3, 28.1 * 10 ** -3])

In [2]: t = np.array([781 * 10 ** -9, 12.4 * 10 ** -6, 188 * 10 ** -6, 2.74 * 10 ** -3, 28.5 * 10 ** -3])

In [11]: np.average(l)
Out[11]: 0.0062112498000000006

In [12]: np.average(t)
Out[12]: 0.0062882362

In [17]: np.average(t) / np.average(l)  * 100
Out[17]: 101.23946713590554

あなたはそれをほとんど決定的でないと呼ぶことができます。

しかし、確かに、タプルはリストに比べて101.239%、時間、または1.239%仕事をするために余分な時間を要しました。


4

タプルは不変であるため、リストよりもわずかに効率的であり、そのためリストよりも高速です。


4
なぜ不変性自体が効率を高めると言うのですか?特にインスタンス化と検索の効率はどうですか?
ブレアコンラッド

1
私の上のMarkの返答は、Pythonの内部で何が起こるかについての逆アセンブルされた指示をカバーしているようです。インスタンス化に必要な命令が少ないことがわかりますが、この場合、取得は明らかに同一です。
ctcherry 2008

不変のタプルは、可変リストよりも速くアクセスできます
noobninja

-6

Tupleが読み取りで非常に効率的である主な理由は、変更できないためです。

なぜ不変オブジェクトは読みやすいのですか?

その理由は、リストとは異なり、タプルはメモリキャッシュに格納できるためです。プログラムは変更可能であるため、常にリストのメモリ位置から読み取ります(いつでも変更できます)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.