web-dev-qa-db-ja.com

Pythonでは100万要素のリストがどのくらいのメモリを消費しますか?

redditmetrics.com によると、Redditには100万を超えるサブレディットがあります。

すべてのsubredditが配列に格納されるまで このReddit APIエンドポイント を繰り返しクエリするスクリプトを作成しました。all_subs

all_subs = []
for sub in <repeated request here>:
    all_subs.append({"name": display_name, "subscribers": subscriber_count})

スクリプトは10時間近く実行されており、ほぼ半分完了しています(3〜4回のリクエストごとにレート制限されます)。終了したら、次のような配列を期待します。

[
    { "name": "AskReddit", "subscribers", 16751677 },
    { "name": "news", "subscribers", 13860169 },
    { "name": "politics", "subscribers", 3350326 },
    ... # plus one million more entries
]

このリストはメモリ内でおよそどのくらいのスペースを占有しますか?

9

これはあなたのPythonバージョンとあなたのシステムに依存しますが、それがどれだけのメモリを必要とするかを理解するためにあなたに手を差し伸べます。最初に最初に、 _sys.getsizeof_ は、コンテナを表すobjectのメモリ使用量のみを返し、コンテナ内のすべての要素は返しません。

オブジェクトに直接起因するメモリ消費のみが考慮され、参照するオブジェクトのメモリ消費は考慮されません。

指定した場合、オブジェクトがサイズを取得する手段を提供しない場合、デフォルトが返されます。そうしないと、TypeErrorが発生します。

getsizeof()は、オブジェクトの___sizeof___メソッドを呼び出し、オブジェクトがガベージコレクターによって管理されている場合は、ガベージコレクターのオーバーヘッドを追加します。

getsizeof()を再帰的に使用してコンテナーのサイズとそのすべての内容を見つける例については、 recursive sizeofレシピ を参照してください。

だから、私はそのレシピをインタラクティブなインタプリタセッションにロードしました:

したがって、CPythonlistは、実際には異種のサイズ変更可能な配列リストです。基になる配列には、Py_Objectsへのポインターのみが含まれます。したがって、ポインタはマシンのワード相当のメモリを占有します。 64ビットシステムでは、これは64ビットなので、8バイトです。したがって、コンテナの場合、サイズ1,000,000のリストは約800万バイト、つまり8メガバイトを占めます。 1000000エントリのリストを作成すると、次のことがわかります。

_In [6]: for i in range(1000000):
   ...:     x.append([])
   ...:

In [7]: import sys

In [8]: sys.getsizeof(x)
Out[8]: 8697464
_

余分なメモリは、pythonオブジェクトのオーバーヘッドと、効率的な_.append_操作を可能にするために基になる配列が最後に残す余分なスペースによって占められます。

現在、Pythonでは辞書はかなり重いです。コンテナだけ:

_In [10]: sys.getsizeof({})
Out[10]: 288
_

したがって、100万ディクトのサイズの下限は288000000バイトです。したがって、大まかな下限:

_In [12]: 1000000*288 + 1000000*8
Out[12]: 296000000

In [13]: 296000000 * 1e-9 # gigabytes
Out[13]: 0.29600000000000004
_

したがって、約0.3ギガバイト相当のメモリが期待できます。レシピとより現実的なdictの使用:

_In [16]: x = []
    ...: for i in range(1000000):
    ...:     x.append(dict(name="my name is what", subscribers=23456644))
    ...:

In [17]: total_size(x)
Out[17]: 296697669

In [18]:
_

だから、約0.3ギグ。さて、それは現代のシステムではそれほど多くありません。ただし、スペースを節約したい場合は、Tupleまたはそれ以上のnamedtupleを使用する必要があります。

_In [24]: from collections import namedtuple

In [25]: Record = namedtuple('Record', "name subscribers")

In [26]: x = []
    ...: for i in range(1000000):
    ...:     x.append(Record(name="my name is what", subscribers=23456644))
    ...:

In [27]: total_size(x)
Out[27]: 72697556
_

または、ギガバイト単位:

_In [29]: total_size(x)*1e-9
Out[29]: 0.07269755600000001
_

namedtupleTupleと同じように機能しますが、namesでフィールドにアクセスできます。

_In [30]: r = x[0]

In [31]: r.name
Out[31]: 'my name is what'

In [32]: r.subscribers
Out[32]: 23456644
_
13