Python'da bir nesnenin boyutunu nasıl belirlerim?
"Sadece sys.getsizeof kullanın" yanıtı tam bir yanıt değildir.
Bu cevap yapar özellikle yerleşiğine için çalışma doğrudan nesneleri, ancak bu nesneler içerebilir neyi dikkate almaz, bu tür özel nesneler, küpe, listeler, dicts ve batarken ne tür içerir. Birbirlerinin örneklerini, sayıları, dizeleri ve diğer nesneleri içerebilirler.
Daha Eksiksiz Bir Yanıt
Anaconda dağıtımından 64 bit Python 3.6 kullanarak, sys.getsizeof ile, aşağıdaki nesnelerin minimum boyutunu belirledim ve setlerin ve diklerin, önceden belirlenmiş bir miktarın sonuna kadar ( dilin uygulamasına göre değişir):
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.
Bunu nasıl yorumluyorsun? İçinde 10 öğe bulunan bir setin olduğunu söyle. Her bir öğenin her biri 100 bayt ise, tüm veri yapısı ne kadar büyüktür? Set 736 kendisidir çünkü bir kez 736 bayta kadar boyutlandırmıştır. Sonra öğelerin boyutunu eklersiniz, bu toplamda 1736 bayttır
Fonksiyon ve sınıf tanımları için bazı uyarılar:
Her sınıf tanımının, __dict__
sınıf izinleri için bir proxy (48 bayt) yapısına sahip olduğunu unutmayın. Her yuva, property
sınıf tanımında bir tanımlayıcıya (a gibi ) sahiptir.
Oluklu örnekler ilk öğelerinde 48 bayt ile başlar ve her biri ek 8 artar. Yalnızca boş oluklu nesneler 16 bayt içerir ve veri içermeyen bir örnek çok az mantıklıdır.
Ayrıca, her işlev tanımının kod nesneleri, belki docstrings ve diğer olası öznitelikleri, hatta bir __dict__
.
Ayrıca sys.getsizeof()
, nesnenin çöp toplama yükünü içeren marjinal alan kullanımını dokümanlardan aldığımız için kullandığımızı da unutmayın :
getsizeof () nesnenin __sizeof__
yöntemini çağırır ve nesne çöp toplayıcı tarafından yönetiliyorsa ek bir çöp toplayıcı ek yükü ekler.
Ayrıca, listelerin yeniden boyutlandırılmasının (örneğin, bunlara tekrar tekrar eklenerek), kümelere ve diktelere benzer şekilde, alanı önceden konumlandırmasına neden olduğunu unutmayın. Gönderen listobj.c kaynak kodu :
/* 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);
Tarihsel veri
Python 2.7 analizi, guppy.hpy
ve ile onaylanmıştır 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.
Sözlüklerin ( ancak kümeler değil ) Python 3.6'da daha kompakt bir temsili olduğunu unutmayın
Ek bir öğe başına 8 baytlık 64 bitlik bir makinede çok anlamlı olduğunu düşünüyorum. Bu 8 bayt, bellekteki öğenin bulunduğu yeri gösterir. 4 bayt Python 2'de unicode için sabit genişliktir, doğru hatırlarsam, ancak Python 3'te str, karakterlerin maksimum genişliğine eşit bir unicode genişliğine dönüşür.
(Ve slotlar hakkında daha fazla bilgi için bu cevaba bakınız )
Daha Eksiksiz Bir İşlev
Listeler, tuples, kümeler, diktler, obj.__dict__
'ler ve obj.__slots__
henüz aklınıza gelmemiş olabilecek diğer şeyleri araştıran bir fonksiyon istiyoruz .
gc.get_referents
Bu aramaya güvenmek istiyoruz çünkü C düzeyinde çalışıyor (çok hızlı yapıyor). Dezavantajı, get_referents'ın gereksiz üyeleri döndürebilmesidir, bu nedenle iki kez saymadığımızdan emin olmamız gerekir.
Sınıflar, modüller ve işlevler tek tonludur - bellekte bir kez bulunurlar. Boyutlarıyla o kadar ilgilenmiyoruz, çünkü onlar hakkında yapabileceğimiz çok şey yok - bunlar programın bir parçası. Bu nedenle, referans gösterildikleri takdirde onları saymaktan kaçınacağız.
Kara liste türlerini kullanacağız, bu nedenle tüm programı boyut sayımımıza dahil etmiyoruz.
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
Bunu aşağıdaki beyaz listeye eklenmiş işlevle karşılaştırmak için, çoğu nesne çöp toplama amacıyla kendilerini nasıl geçeceğini bilir (bu, belirli nesnelerin belleğinde ne kadar pahalı olduğunu bilmek istediğimizde yaklaşık olarak aradığımız şeydir. gc.get_referents
.) Ancak, bu önlem, dikkatli olmazsak, kapsamda düşündüğümüzden çok daha geniş olacaktır.
Örneğin, işlevler oluşturuldukları modüller hakkında çok şey bilir.
Diğer bir kontrast noktası, sözlüklerde anahtar olan dizelerin çoğaltılmaması için genellikle stajyer olmasıdır. Denetleniyor id(key)
da bize bir sonraki bölümde ne sayım çiftleri önlemek sağlayacaktır. Kara liste çözümü, dizeler olan sayma anahtarlarını tamamen atlar.
Beyaz Listeye Alınan Türler, Yinelemeli ziyaretçi (eski uygulama)
Bu türlerin çoğunu kendim kapsayacak şekilde, gc modülüne güvenmek yerine, çoğu yinelenen, koleksiyon modülündeki türler ve özel türler (oluklu ve diğer) dahil olmak üzere çoğu Python nesnesinin boyutunu tahmin etmeye çalışmak için bu özyinelemeli işlevi yazdım .
Bu tür bir işlev, bellek kullanımı için sayacağımız türler üzerinde çok daha hassas bir kontrol sağlar, ancak türleri dışarıda bırakma tehlikesi vardır:
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)
Ve oldukça rahat bir şekilde test ettim (birimtest etmeliyim):
>>> 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
Bu uygulama, sınıf özniteliklerini ve işlev tanımlarını yıkar, çünkü tüm özniteliklerinin peşinden gitmeyiz, ancak işlem için yalnızca bir kez bellekte bulunması gerektiğinden, boyutları gerçekten çok fazla önemli değildir.