web-dev-qa-db-ja.com

Pythonのリストよりタプルの方が速いのはなぜですか?

"Dive into Python" で「タプルはリストよりも速い」と読んだところです。

Tupleは不変で、listは変更可能ですが、Tupleの方が速い理由がよくわかりません。

誰もがこれでパフォーマンステストをしましたか?

68
Quan Mai

報告された「構築速度」の比率は定数タプル(項目がリテラルで表されるもの)に対してのみ適用されます。注意深く観察してください(そしてあなたのマシンで繰り返します-あなたはシェル/コマンドウィンドウでコマンドを入力するだけです!)...:

$ python3.1 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.379 usec per loop
$ python3.1 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.413 usec per loop

$ python3.1 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.174 usec per loop
$ python3.1 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0602 usec per loop

$ python2.6 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.352 usec per loop
$ python2.6 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.358 usec per loop

$ python2.6 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.157 usec per loop
$ python2.6 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0527 usec per loop

3.0の測定は行いませんでした。もちろん、私にはそれがないためです。これは完全に時代遅れであり、3.1はあらゆる点で優れているため(Python 2.7、これにアップグレードでき、各タスクで2.6よりも約20%速く測定されます-ご覧のように、2.6は3.1よりも高速です-したがって、パフォーマンスを重視する場合は、Python = 2.7は、実際にリリースする唯一のリリースです!)。

とにかく、ここでの重要な点は、各Python=リリースで、定数リテラルからリストを構築することは、変数によって参照される値から構築することとほぼ同じ速度または少し遅いことです。しかし、タプルの動作は非常に異なります。定数リテラルからタプルを構築すると、通常、変数によって参照される値からタプルを構築する場合の3倍の速さで処理されます!これはどのように行われるのでしょうか。

回答:定数リテラルで作成されたタプルは、Pythonコンパイラーが1つの不変定数リテラル自体であると簡単に識別できます。つまり、コンパイラーがソースをバイトコードに変換するときに、基本的に一度だけ構築されます。 、そして関連する関数またはモジュールの「定数テーブル」に隠されています。これらのバイトコードが実行されると、事前に構築された定数Tupleを回復する必要があります-ねえpresto!-)

リストは変更可能なオブジェクトであるため、この簡単な最適化はリストに適用できません。したがって、[1, 2, 3]などの同じ式が2回実行される場合(ループ内でtimeitモジュールがあなたに代わってループ;-)、新しい新しいリストオブジェクトが毎回新しく構築されます-そして、その構築(コンパイラがコンパイル時定数および不変オブジェクトとしてそれを自明に識別できない場合のタプルの構築のように)には、しばらく。

とは言っても、タプルの構造(実際に両方の構造が発生する必要がある場合)は、リストの構造の約2倍の速度です-その矛盾は説明できます他の回答が繰り返し言及しているタプルの純粋な単純さによって。しかし、リストとタプルの構造を単純な定数リテラルとそれらのアイテムとして比較するだけであれば、その単純さは6倍以上のスピードアップを考慮していません!

84
Alex Martelli

アレックスは素晴らしい答えを出しましたが、私が言及する価値があると思ういくつかの事柄を拡張してみましょう。パフォーマンスの違いは一般に小さく、実装固有です。したがって、ファームにそれらをかけないでください。

CPythonでは、タプルは単一のメモリブロックに格納されるため、新しいタプルを作成するには、最悪の場合、メモリを割り当てるための単一の呼び出しが必要です。リストは2つのブロックに割り当てられます。すべてのPythonオブジェクト情報を含む固定ブロックと、データ用の可変サイズのブロックです。これがタプルの作成が速い理由の一部ですが、おそらくに従うポインタが1つ少ないため、インデックス作成速度のわずかな違いについて説明します。

CPythonには、メモリ割り当てを減らすための最適化もあります。割り当て解除されたリストオブジェクトはフリーリストに保存されるため、再利用できますが、空でないリストを割り当てるには、データ用のメモリ割り当てが必要です。タプルはサイズの異なるタプルの20個の空きリストに保存されるため、小さなタプルを割り当てる場合、多くの場合、メモリ割り当ての呼び出しはまったく必要ありません。

このような最適化は実際には役立ちますが、「timeit」の結果に大きく依存しすぎる危険性があり、メモリ割り当てがまったく異なるIronPythonのようなものに移動すると、もちろん完全に異なります。

19
Duncan

timeitモジュールの機能により、パフォーマンスに関連する質問を自分で解決できることがよくあります。

$ python2.6 -mtimeit -s 'a = Tuple(range(10000))' 'for i in a: pass'
10000 loops, best of 3: 189 usec per loop
$ python2.6 -mtimeit -s 'a = list(range(10000))' 'for i in a: pass' 
10000 loops, best of 3: 191 usec per loop

これは、タプルがリストよりも無視できるほど高速であることを示しています。インデックス作成でも同様の結果が得られますが、構築ではタプルがリストを破棄します。

$ python2.6 -mtimeit '(1, 2, 3, 4)'   
10000000 loops, best of 3: 0.0266 usec per loop
$ python2.6 -mtimeit '[1, 2, 3, 4]'
10000000 loops, best of 3: 0.163 usec per loop

したがって、反復またはインデックス作成の速度が唯一の要因である場合、実質的に違いはありませんが、構築の場合、タプルが優先されます。

16
Alec Thomas

エグゼクティブサマリー

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

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.
 */
_

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

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

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

_$ 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;
_

Tupleオブジェクトは2つのデータポインターを直接組み込んでいますが、リストオブジェクトは2つのデータポインターを保持する外部配列への間接層を追加しています。

14

本質的にはタプルの不変性は、インタプリタがリストと比較して、よりスリムで高速なデータ構造を使用できることを意味するためです。

5
Dan Breslau

リストが著しく高速な領域の1つは、ジェネレーターからの構築です。特に、リスト内包表記は、最も近いタプルの同等物であるTuple()とジェネレーター引数を使用した場合よりもはるかに高速です。

_$ python --version
Python 3.6.0rc2
$ python -m timeit 'Tuple(x * 2 for x in range(10))'
1000000 loops, best of 3: 1.34 usec per loop
$ python -m timeit 'list(x * 2 for x in range(10))'
1000000 loops, best of 3: 1.41 usec per loop
$ python -m timeit '[x * 2 for x in range(10)]'
1000000 loops, best of 3: 0.864 usec per loop
_

特に、Tuple(generator)list(generator)よりも少し高速に見えますが、_[elem for elem in generator]_はどちらよりも高速であることに注意してください。

1
Dan Passaro

タプルはpythonコンパイラによって1つの不変定数として識別されるため、コンパイラはハッシュテーブルに1つのエントリのみを作成し、変更されませんでした。

リストは変更可能なオブジェクトであるため、リストを更新するとコンパイラーがエントリを更新するため、タプルと比較すると少し遅くなります

0
y durga prasad

Pythonでは、2種類のオブジェクトがあります。 1。可変2。不変
Pythonでは、リストは可変オブジェクトの下にあり、タプルは不変オブジェクトの下にあります。

  • タプルは単一のメモリブロックに格納されます。タプルは不変なので、新しいオブジェクトを格納するために余分なスペースを必要としません。

  • リストは2つのブロックに割り当てられます。すべてのPythonオブジェクト情報を含む固定ブロックと、データ用の可変サイズのブロックです。

  • これがタプルの作成がリストよりも速い理由です。

  • また、インデックス作成のタプルでは追跡されるポインタが少ないため、インデックス作成速度のわずかな違いがリストよりも速いことも説明しています。

タプルを使用する利点:

  • タプルは、リストがより多くのメモリを使用するのに対し、より少ないメモリを使用することです。

  • 辞書のタプルをキーとして使用できますが、リストでは使用できません。

  • タプルとリストの両方でインデックスを持つ要素にアクセスできます。

タプルの欠点:

  • タプルに要素を追加することはできませんが、リストに要素を追加することはできます。

  • タプルをソートすることはできませんが、リストではlist.sort()メソッドを呼び出すことでソートできます。

  • タプルでは要素を削除できませんが、リストでは要素を削除できます。

  • タプルの要素を置き換えることはできませんが、リストでは置き換えることができます。


ソース

0
M. Rostami