1. ホーム
  2. python

[解決済み] なぜタプルはリストよりも少ないメモリ容量で済むのですか?

2022-09-10 23:31:15

質問

A tuple はPythonではより少ないメモリスペースで動作します。

>>> a = (1,2,3)
>>> a.__sizeof__()
48

ここで list はより多くのメモリ空間を必要とします。

>>> b = [1,2,3]
>>> b.__sizeof__()
64

Pythonのメモリ管理は内部でどうなっているのでしょうか?

どのように解決するのですか?

CPythonを64ビットで使用しているのでしょう(私はCPython 2.7 64ビットで同じ結果を得ました)。他の Python の実装や、32 ビットの Python を使用している場合は、違いがある可能性があります。

実装に関係なく list は可変サイズであるのに対し tuple は固定サイズです。

そのため tuple は構造体の中に直接要素を格納できますが、リストは間接的なレイヤーを必要とします(要素へのポインタを格納します)。この間接参照層はポインターで、64ビットシステムでは64ビット、つまり8バイトになります。

しかし、もうひとつ list が行うもうひとつのこと、それは過剰なアロケーションです。そうでなければ list.appendO(n) 操作 常に - 償却させるために O(1) (ずっと速い!!) にするために、過剰に割り当てるのです。しかし、今度は が割り当てられた のサイズと で満たされている サイズ ( tuple は1つのサイズしか保存する必要がありません。なぜなら、割り当てられたサイズと充填されたサイズは常に同一だからです。) つまり、各リストは別の "size" を格納する必要があり、これは 64 ビット システムでは 64 ビットの整数で、再び 8 バイトになります。

そこで list よりも少なくとも 16 バイト多いメモリが必要です。 tuple s. なぜ私は「少なくとも」と言ったのでしょうか?それは、オーバーアロケーションがあるからです。オーバーアロケーションとは、必要以上のスペースを割り当てるということです。ただし、オーバーアロケーションの量は、リストの作成方法と追加/削除の履歴に依存します。

>>> l = [1,2,3]
>>> l.__sizeof__()
64
>>> l.append(4)  # triggers re-allocation (with over-allocation), because the original list is full
>>> l.__sizeof__()
96

>>> l = []
>>> l.__sizeof__()
40
>>> l.append(1)  # re-allocation with over-allocation
>>> l.__sizeof__()
72
>>> l.append(2)  # no re-alloc
>>> l.append(3)  # no re-alloc
>>> l.__sizeof__()
72
>>> l.append(4)  # still has room, so no over-allocation needed (yet)
>>> l.__sizeof__()
72

画像

上記の説明に付随して、いくつかの画像を作成することにしました。たぶん、これらは役に立つでしょう

あなたの例では、このように(模式的に)メモリに格納されています。違いを赤の(フリーハンドの)サイクルで強調しました。

これは実際には単なる近似値です。 int オブジェクトは Python のオブジェクトでもあり、CPython は小さな整数さえも再利用するので、メモリ上のオブジェクトをおそらくより正確に表現すると(読みやすくはありませんが)、次のようになります。

便利なリンク集です。

注意点として __sizeof__ は実際には "正しい" サイズを返しません! これは、格納されている値のサイズを返すだけです。しかし sys.getsizeof を使用すると、結果は異なります。

>>> import sys
>>> l = [1,2,3]
>>> t = (1, 2, 3)
>>> sys.getsizeof(l)
88
>>> sys.getsizeof(t)
72

24 バイトの "extra" があります。これらは 本当の で考慮されていないガベージコレクタのオーバーヘッドです。 __sizeof__ メソッドでは考慮されないガベージコレクタのオーバーヘッドです。それは、一般的にマジックメソッドを直接使用することは想定されていないからです。 sys.getsizeof (これは実際には は GC オーバーヘッドを追加します。 から返される値に __sizeof__ ).