Pythonでオブジェクトのサイズを確認するにはどうすればよいですか?
「ただsys.getsizeofを使用する」という答えは完全な答えではありません。
その答えは組み込みオブジェクトに対して直接機能しますが、それらのオブジェクトに含まれる可能性のあるもの、特にカスタムオブジェクト、タプル、リスト、ディクショナリ、セットなどのタイプに含まれるものは考慮されません。これらには、相互にインスタンスを含めることができ、数値、文字列、その他のオブジェクトを含めることができます。
より完全な答え
Anacondaディストリビューションの64ビットPython 3.6とsys.getsizeofを使用して、次のオブジェクトの最小サイズを決定しました。また、セットとディクテーションによって事前に割り当てられたスペースが空になるので、設定された量になるまで(つまり、言語の実装によって異なります):
Python 3:
Empty
Bytes type scaling notes
28 int +4 bytes about every 30 powers of 2
37 bytes +1 byte per additional byte
49 str +1-4 per additional character (depending on max width)
48 tuple +8 per additional item
64 list +8 for each additional
224 set 5th increases to 736; 21nd, 2272; 85th, 8416; 341, 32992
240 dict 6th increases to 368; 22nd, 1184; 43rd, 2280; 86th, 4704; 171st, 9320
136 func def does not include default args and other attrs
1056 class def no slots
56 class inst has a __dict__ attr, same scaling as dict above
888 class def with slots
16 __slots__ seems to store in mutable tuple-like structure
first slot grows to 48, and so on.
これをどのように解釈しますか?10個のアイテムが入ったセットがあるとしましょう。各アイテムがそれぞれ100バイトの場合、データ構造全体の大きさはどれくらいですか?セットのサイズは736バイトなので、一度にサイズが736バイトになっています。次に、アイテムのサイズを追加すると、合計で1736バイトになります。
関数とクラスの定義に関する注意事項:
各クラス定義には、__dict__
クラス属性のプロキシ(48バイト)構造があることに注意してください。各スロットproperty
のクラス定義には(のような)記述子があります。
スロット付きインスタンスは、最初の要素が48バイトで始まり、追加ごとに8バイトずつ増加します。空のスロットオブジェクトのみが16バイトであり、データのないインスタンスはほとんど意味がありません。
また、各関数の定義には、コードオブジェクト(おそらくdocstrings)やその他の可能な属性が含まれます__dict__
。
また、我々が使用していることに注意してくださいsys.getsizeof()
、我々はオブジェクトのガベージコレクションのオーバーヘッド、含まマージナルスペースの使用状況、気にするので、ドキュメントからの:
getsizeof()はオブジェクトの__sizeof__
メソッドを呼び出し、オブジェクトがガベージコレクターによって管理されている場合は、ガベージコレクターのオーバーヘッドを追加します。
また、リストのサイズを変更すると(リストに繰り返し追加するなど)、セットや辞書と同様に、スペースが事前に割り当てられます。listobj.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.
*/
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
歴史的なデータ
guppy.hpy
およびで確認されたPython 2.7分析sys.getsizeof
:
Bytes type empty + scaling notes
24 int NA
28 long NA
37 str + 1 byte per additional character
52 unicode + 4 bytes per additional character
56 tuple + 8 bytes per additional item
72 list + 32 for first, 8 for each additional
232 set sixth item increases to 744; 22nd, 2280; 86th, 8424
280 dict sixth item increases to 1048; 22nd, 3352; 86th, 12568 *
120 func def does not include default args and other attrs
64 class inst has a __dict__ attr, same scaling as dict above
16 __slots__ class with slots has no dict, seems to store in
mutable tuple-like structure.
904 class def has a proxy __dict__ structure for class attrs
104 old class makes sense, less stuff, has real dict though.
辞書(セットではない)は、Python 3.6でよりコンパクトに表現されていることに注意してください。
参照する追加のアイテムごとに8バイトは、64ビットマシンでは非常に理にかなっていると思います。これらの8バイトは、格納されているアイテムが存在するメモリ内の場所を指します。Python 2では、4バイトはユニコードの固定幅ですが、Python 3では、strは文字の最大幅に等しい幅のユニコードになります。
(そしてスロットの詳細については、この回答を参照してください)
より完全な機能
リスト、タプル、セット、ディクショナリ、、、obj.__dict__
およびobj.__slots__
まだ考えていないその他の要素を検索する関数が必要です。
gc.get_referents
この検索はCレベルで機能するため(非常に高速になるため)、信頼できる検索を行いたいと考えています。欠点は、get_referentsが冗長なメンバーを返す可能性があるため、二重にカウントしないようにする必要があります。
クラス、モジュール、関数はシングルトンです-それらはメモリ内に一度存在します。それらについて私たちができることはほとんどないので、私たちはそれらのサイズにそれほど興味はありません-それらはプログラムの一部です。したがって、それらがたまたま参照された場合、それらをカウントすることは避けます。
タイプのブラックリストを使用するので、プログラム全体をサイズカウントに含めません。
import sys
from types import ModuleType, FunctionType
from gc import get_referents
# Custom objects know their class.
# Function objects seem to know way too much, including modules.
# Exclude modules as well.
BLACKLIST = type, ModuleType, FunctionType
def getsize(obj):
"""sum size of object & members."""
if isinstance(obj, BLACKLIST):
raise TypeError('getsize() does not take argument of type: '+ str(type(obj)))
seen_ids = set()
size = 0
objects = [obj]
while objects:
need_referents = []
for obj in objects:
if not isinstance(obj, BLACKLIST) and id(obj) not in seen_ids:
seen_ids.add(id(obj))
size += sys.getsizeof(obj)
need_referents.append(obj)
objects = get_referents(*need_referents)
return size
これを次のホワイトリスト関数と対比すると、ほとんどのオブジェクトは、ガベージコレクションの目的で自分自身をトラバースする方法を知っています(これは、特定のオブジェクトがメモリ内でどれほど高価であるかを知りたいときにおおよそ探しているものです。この機能は、gc.get_referents
。)ただし、この措置は、注意しないと、意図した範囲よりもはるかに広範囲になります。
たとえば、関数は、それらが作成されたモジュールについてかなり知っています。
対照的なもう1つのポイントは、辞書のキーである文字列は通常インターンされるため、重複しないことです。以下のためにチェックすることid(key)
も私たちは、次のセクションで行うカウントの重複を回避することができます。ブラックリストソリューションは、文字列であるキーのカウントをすべてスキップします。
ホワイトリストに登録されたタイプ、再帰的なビジター(古い実装)
これらのタイプのほとんどを自分でカバーするために、gcモジュールに依存する代わりに、この再帰関数を作成して、ほとんどの組み込みオブジェクト、コレクションモジュールのタイプ、カスタムタイプ(スロットなど)を含む、ほとんどのPythonオブジェクトのサイズを推定しようとしました。
この種の関数は、メモリ使用量として数えるタイプをはるかに細かく制御しますが、タイプを除外する危険があります。
import sys
from numbers import Number
from collections import Set, Mapping, deque
try: # Python 2
zero_depth_bases = (basestring, Number, xrange, bytearray)
iteritems = 'iteritems'
except NameError: # Python 3
zero_depth_bases = (str, bytes, Number, range, bytearray)
iteritems = 'items'
def getsize(obj_0):
"""Recursively iterate to sum size of object & members."""
_seen_ids = set()
def inner(obj):
obj_id = id(obj)
if obj_id in _seen_ids:
return 0
_seen_ids.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, zero_depth_bases):
pass # bypass remaining control flow and return
elif isinstance(obj, (tuple, list, Set, deque)):
size += sum(inner(i) for i in obj)
elif isinstance(obj, Mapping) or hasattr(obj, iteritems):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, iteritems)())
# Check for custom object instances - may subclass above too
if hasattr(obj, '__dict__'):
size += inner(vars(obj))
if hasattr(obj, '__slots__'): # can have __slots__ with __dict__
size += sum(inner(getattr(obj, s)) for s in obj.__slots__ if hasattr(obj, s))
return size
return inner(obj_0)
そして私はそれをかなりカジュアルにテストしました(私はそれを単体テストする必要があります):
>>> getsize(['a', tuple('bcd'), Foo()])
344
>>> getsize(Foo())
16
>>> getsize(tuple('bcd'))
194
>>> getsize(['a', tuple('bcd'), Foo(), {'foo': 'bar', 'baz': 'bar'}])
752
>>> getsize({'foo': 'bar', 'baz': 'bar'})
400
>>> getsize({})
280
>>> getsize({'foo':'bar'})
360
>>> getsize('foo')
40
>>> class Bar():
... def baz():
... pass
>>> getsize(Bar())
352
>>> getsize(Bar().__dict__)
280
>>> sys.getsizeof(Bar())
72
>>> getsize(Bar.__dict__)
872
>>> sys.getsizeof(Bar.__dict__)
280
この実装は、クラス定義と関数定義に分類されます。すべての属性を追跡するわけではないためですが、これらはプロセスのメモリに1回だけ存在する必要があるため、サイズはそれほど重要ではありません。