さまざまなpythonデータ型のメモリ消費については、たくさんの質問と議論があります。それでも、非常に具体的なシナリオに当てはまるものはあります(ある場合)。大量のキー値データをメモリに格納する場合、メモリ効率の良いデータ構造は、dictまたはタプルのリストですか?
最初は、dictはタプルのリストよりも強力であり、その力はある程度の代償を伴う必要があると思いました。実際、空のdictは空のリストまたはタプルよりも多くのメモリを占有します( In-memory size of a Python構造 )なので、[(key1, value1), (key2, value2), ...]
を使用すると{key1: value1, key2: value2, ...}
よりもメモリ効率が良くなると思いました。
私は間違っていたようです。次のコードスニペットを起動して、OSによって報告されたメモリ消費を確認してください。私はWindows XPを使用しているため、タスクマネージャーが言うように、大きな辞書は40MBのRAMと40MBの仮想RAMだけを消費しますが、タプルのリストは60MBのRAMと60MBの仮想RAMを消費します。
それはどうでしょうか?
from sys import getsizeof as g
raw_input('ready, press ENTER')
i = 1000000
#p = [(x, x) for x in xrange(i)] # Will print 4,348,736 40,348,736
p = dict((x, x) for x in xrange(i)) # Will print 25,165,964 37,165,964
print g(p), g(p) + sum(g(x) for x in p)
raw_input("Check your process's memory consumption now, press ENTER to exit")
更新:
以下のコメントの一部をありがとう。明確にしたいのですが、私はメモリ効率について話しています。そして、いいえ、この場合、キーと値のルックアップ効率を心配する必要はありません。私のアルゴリズムがイテレータを介して1つずつそれらを消費すると仮定しましょう。
list
/Tuple
sを追加すると、レイヤーが追加されます。 アイテムのレイヤーがあります:
あなたのdict
は以下のみを保持します:
100万タプルとそれらへの参照を保持するためのリストは、100万キャッシュハッシュよりも多くのメモリを消費します。ここに含まれるポインタの数は50%増えており、メモリ使用量が50%多いことを簡単に説明できます。
タプルのリストにはもう1つの欠点があります。それは、ルックアップ時間です。辞書で一致するキーを見つけるには、O(1)複雑度コストがあります。タプルのリストで同じことを行うには、O(n)コストについてリスト全体をスキャンする必要がある可能性があります。キーを値にマップする必要がある場合は、タプルのリストを使用しないでください。
この場合、実際にはメモリ使用の不完全な画像が表示されます。ディクショナリの合計サイズは不規則な間隔で2倍を超えます。ディクショナリのサイズが増加した直後にこれら2つの構造のサイズを比較すると、サイズは再び大きくなります。再帰的なサイズ関数(以下のコードを参照)を使用した単純なスクリプトは、かなり明確なパターンを示しています。
i: 2 list size: 296 dict size: 328 difference: -32
i: 3 list size: 392 dict size: 352 difference: 40
i: 4 list size: 488 dict size: 376 difference: 112
i: 5 list size: 616 dict size: 400 difference: 216
i: 7 list size: 808 dict size: 1216 difference: -408
i: 10 list size: 1160 dict size: 1288 difference: -128
i: 13 list size: 1448 dict size: 1360 difference: 88
i: 17 list size: 1904 dict size: 1456 difference: 448
i: 23 list size: 2480 dict size: 3904 difference: -1424
i: 31 list size: 3328 dict size: 4096 difference: -768
i: 42 list size: 4472 dict size: 4360 difference: 112
i: 56 list size: 5912 dict size: 4696 difference: 1216
i: 74 list size: 7880 dict size: 5128 difference: 2752
i: 100 list size: 10520 dict size: 14968 difference: -4448
i: 133 list size: 14024 dict size: 15760 difference: -1736
i: 177 list size: 18672 dict size: 16816 difference: 1856
このパターンは、i
が大きくなるにつれて続きます。 (あなたはあなたの方法を使ってこれをテストすることができます-i
を2636744
の近くに設定してみてください。辞書のサイズはその時点で、少なくとも私にとっては大きいです。) Martijn タプルのリスト内のタプルがメモリのオーバーヘッドを増やし、辞書よりもリストのメモリの利点を相殺するのは正しいことです。しかし、平均して、結果は辞書がより良いということではありません。それは辞書がほぼ同じだということです。だからあなたの元の質問に答えて:
大量のキー値データをメモリに格納する場合、メモリ効率の良いデータ構造は、dictまたはタプルのリストですか?
あなたが心配しているのがメモリだけなら、それは本当に問題ではありません。
ただし、すべての空のビンの反復を回避する良い方法がないため、辞書の反復はリストの反復よりも少し遅いことに注意してください辞書で。したがって、トレードオフが少しあります。辞書はランダムなキールックアップを実行する際に(はるかに)高速ですが、リストは反復時に(少し)高速です。ほとんどの場合、辞書の方が優れていますが、まれに、リストがマイクロ最適化を提供する場合があります。
サイズをテストするコードは次のとおりです。すべてのコーナーケースで正しい結果が生成されるとは限りませんが、このような単純な構造を問題なく処理できます。 (ただし、問題が発生した場合はお知らせください。)
import sys, collections, itertools, math
def totalsize(x):
seen = set()
return ts_rec(x, seen)
def ts_rec(x, seen):
if id(x) in seen:
return 0
else:
seen.add(id(x))
x_size = sys.getsizeof(x)
if isinstance(x, collections.Mapping):
kv_chain = itertools.chain.from_iterable(x.iteritems())
return x_size + sum(ts_rec(i, seen) for i in kv_chain)
Elif isinstance(x, collections.Sequence):
return x_size + sum(ts_rec(i, seen) for i in x)
else:
return x_size
for i in (10 ** (e / 8.0) for e in range(3, 19)):
i = int(i)
lsize = totalsize([(x, x) for x in xrange(i)])
dsize = totalsize(dict((x, x) for x in xrange(i)))
print "i: ", i,
print " list size: ", lsize, " dict size: ", dsize,
print " difference: ", lsize - dsize