Pythonでオブジェクトのサイズを決定するにはどうすればよいですか?
「ただ使うだけsys.getsizeof
」という答えは、完全な答えではありません。
その答えは組み込みオブジェクトに対して直接機能しますが、それらのオブジェクトに含まれる可能性があるもの、具体的には、カスタム オブジェクト、タプル、リスト、ディクテーション、セットなどの型が含まれているかどうかは考慮されていません。それらには、数値、文字列、その他のオブジェクトだけでなく、インスタンス同士を含めることができます。
より完全な答え
Anaconda ディストリビューションの 64 ビット Python 3.6 を使用してsys.getsizeof
、次のオブジェクトの最小サイズを決定しました。set と dict はスペースを事前に割り当てるため、空のオブジェクトは一定量 (言語の実装):
パイソン 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 バイトを持ち、データのないインスタンスはほとんど意味がありません。
また、各関数定義には、コード オブジェクト (docstring など) やその他の可能な属性 ( __dict__
.
docs から、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 では、辞書 ( set ではない) の表現がよりコンパクトになったことに注意してください。
64ビットマシンでは、参照する追加アイテムごとに8バイトが非常に理にかなっていると思います。これらの 8 バイトは、含まれている項目があるメモリ内の場所を指します。私が正しく思い出すと、4バイトはPython 2のユニコードの固定幅ですが、Python 3では、 str は文字の最大幅に等しい幅のユニコードになります。
スロットの詳細については、この回答を参照してください。
より完全な機能
リスト、タプル、セット、dicts、obj.__dict__
's、および の要素を検索する関数と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 deque
from collections.abc import Set, Mapping
ZERO_DEPTH_BASES = (str, bytes, Number, range, bytearray)
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, 'items'):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, 'items')())
# 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
この実装は、クラス定義と関数定義のすべての属性を追跡するわけではないため、分解されますが、それらはプロセスのメモリ内に一度だけ存在する必要があるため、それらのサイズは実際にはあまり重要ではありません。