web-dev-qa-db-ja.com

Pythonメモリ消費:dict VSタプルのリスト

さまざまな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つずつそれらを消費すると仮定しましょう。

22
RayLuo

list/Tuplesを追加すると、レイヤーが追加されます。 アイテムのレイヤーがあります:

  • 長さ100万の外部リスト、つまり100万のポインタ
    • 100万の2スロットタプル、つまり200万のポインター
      • 200万の参照から100万の整数値

あなたのdictは以下のみを保持します:

  • テーブルを拡張するための200万のポインタ+追加のスペースを持つ辞書(100万のキャッシュされたハッシュを含む)
    • 200万の参照から100万の整数値

100万タプルとそれらへの参照を保持するためのリストは、100万キャッシュハッシュよりも多くのメモリを消費します。ここに含まれるポインタの数は50%増えており、メモリ使用量が50%多いことを簡単に説明できます。

タプルのリストにはもう1つの欠点があります。それは、ルックアップ時間です。辞書で一致するキーを見つけるには、O(1)複雑度コストがあります。タプルのリストで同じことを行うには、O(n)コストについてリスト全体をスキャンする必要がある可能性があります。キーを値にマップする必要がある場合は、タプルのリストを使用しないでください。

26
Martijn Pieters

この場合、実際にはメモリ使用の不完全な画像が表示されます。ディクショナリの合計サイズは不規則な間隔で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が大きくなるにつれて続きます。 (あなたはあなたの方法を使ってこれをテストすることができます-i2636744の近くに設定してみてください。辞書のサイズはその時点で、少なくとも私にとっては大きいです。) 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
10
senderle