Bagaimana cara menentukan ukuran suatu objek dengan Python?
Jawabannya, "Cukup gunakan sys.getsizeof" bukan jawaban yang lengkap.
Jawaban itu memang berfungsi untuk objek builtin secara langsung, tetapi tidak menjelaskan apa yang mungkin berisi objek-objek itu, khususnya, jenis apa, seperti objek kustom, tupel, daftar, dikt, dan set berisi. Mereka dapat berisi instance satu sama lain, serta angka, string dan objek lainnya.
Jawaban yang Lebih Lengkap
Menggunakan 64 bit Python 3.6 dari distribusi Anaconda, dengan sys.getsizeof, saya telah menentukan ukuran minimum dari objek berikut, dan perhatikan bahwa set dan dicts mengalokasikan ruang sehingga yang kosong tidak tumbuh lagi sampai setelah jumlah yang ditentukan (yang mungkin bervariasi berdasarkan implementasi bahasa):
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.
Bagaimana Anda menafsirkan ini? Nah katakanlah Anda memiliki satu set dengan 10 item di dalamnya. Jika setiap item masing-masing 100 byte, seberapa besar seluruh struktur data? Set adalah 736 itu sendiri karena memiliki ukuran satu kali hingga 736 byte. Kemudian Anda menambahkan ukuran item, sehingga totalnya adalah 1736 byte
Beberapa peringatan untuk definisi fungsi dan kelas:
Perhatikan bahwa setiap definisi kelas memiliki struktur proxy __dict__
(48 byte) untuk attr kelas. Setiap slot memiliki deskriptor (seperti a property
) dalam definisi kelas.
Mesin Virtual Slotted memulai dengan 48 byte pada elemen pertama mereka, dan meningkat 8 masing-masing tambahan. Hanya objek slotted kosong yang memiliki 16 byte, dan sebuah instance tanpa data masuk akal sangat sedikit.
Juga, setiap definisi fungsi memiliki objek kode, mungkin dokumen, dan atribut lainnya yang mungkin, bahkan a __dict__
.
Perhatikan juga bahwa kami menggunakan sys.getsizeof()
karena kami peduli tentang penggunaan ruang marginal, yang mencakup pengumpulan sampah di atas objek, dari dokumen :
getsizeof () memanggil __sizeof__
metode objek dan menambahkan overhead pengumpul sampah tambahan jika objek dikelola oleh pengumpul sampah.
Perhatikan juga bahwa mengubah ukuran daftar (misalnya menambahkannya secara berulang) menyebabkan mereka untuk melakukan pra-alokasi ruang, mirip dengan set dan dikt. Dari kode sumber 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);
Data historis
Analisis Python 2.7, dikonfirmasi dengan guppy.hpy
dan 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.
Perhatikan bahwa kamus ( tetapi bukan set ) mendapat representasi yang lebih ringkas dalam Python 3.6
Saya pikir 8 byte per item tambahan untuk referensi masuk akal pada mesin 64 bit. 8 byte itu menunjuk ke tempat di memori item yang terkandung. 4 byte adalah lebar tetap untuk unicode di Python 2, jika saya ingat dengan benar, tetapi dalam Python 3, str menjadi unicode dengan lebar sama dengan lebar maks karakter.
(Dan untuk lebih lanjut tentang slot, lihat jawaban ini )
Fungsi Yang Lebih Lengkap
Kami ingin fungsi yang mencari elemen dalam daftar, tupel, set, dikte obj.__dict__
, danobj.__slots__
, serta hal-hal lain yang mungkin belum terpikirkan.
Kami ingin mengandalkan gc.get_referents
untuk melakukan pencarian ini karena berfungsi pada level C (membuatnya sangat cepat). Kelemahannya adalah get_referents dapat mengembalikan anggota yang berlebihan, jadi kami perlu memastikan bahwa kami tidak menggandakan jumlah.
Kelas, modul, dan fungsi adalah lajang - mereka ada satu kali dalam memori. Kami tidak begitu tertarik dengan ukurannya, karena tidak banyak yang dapat kami lakukan tentang mereka - mereka adalah bagian dari program ini. Jadi kami akan menghindari menghitungnya jika mereka dirujuk.
Kami akan menggunakan daftar jenis hitam sehingga kami tidak memasukkan seluruh program dalam jumlah ukuran kami.
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
Untuk mengontraskan ini dengan fungsi daftar putih berikut, sebagian besar objek tahu cara melintasi dirinya untuk tujuan pengumpulan sampah (yang kira-kira apa yang kita cari ketika kita ingin tahu seberapa mahal dalam memori objek tertentu. Fungsi ini digunakan oleh gc.get_referents
.) Namun, ukuran ini akan jauh lebih luas dalam ruang lingkup daripada yang kita maksudkan jika kita tidak hati-hati.
Sebagai contoh, fungsi tahu banyak tentang modul yang mereka buat.
Poin kontras lainnya adalah string yang merupakan kunci dalam kamus biasanya diinternir sehingga tidak terduplikasi. Memeriksa id(key)
juga akan memungkinkan kami untuk menghindari penghitungan duplikat, yang kami lakukan di bagian selanjutnya. Solusi daftar hitam melompati tombol penghitungan yang merupakan string sekaligus.
Jenis Daftar Putih, pengunjung Rekursif (implementasi lama)
Untuk menutupi sebagian besar dari tipe ini sendiri, daripada mengandalkan modul gc, saya menulis fungsi rekursif ini untuk mencoba memperkirakan ukuran sebagian besar objek Python, termasuk sebagian besar builtin, tipe dalam modul koleksi, dan tipe khusus (slotted dan lainnya) .
Fungsi semacam ini memberikan kontrol yang jauh lebih baik atas jenis yang akan kita hitung untuk penggunaan memori, tetapi memiliki risiko mengabaikan jenis:
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)
Dan saya mengujinya dengan santai (saya harus melepasnya):
>>> 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
Implementasi ini rusak pada definisi kelas dan definisi fungsi karena kita tidak mengejar semua atribut mereka, tetapi karena mereka seharusnya hanya ada satu kali dalam memori untuk proses, ukurannya benar-benar tidak terlalu penting.