ฉันจะกำหนดขนาดของวัตถุใน Python ได้อย่างไร
คำตอบ "เพียงแค่ใช้ sys.getsizeof" ไม่ใช่คำตอบที่สมบูรณ์
คำตอบนั้นใช้ได้กับวัตถุบิวด์อินโดยตรง แต่ไม่ได้อธิบายถึงสิ่งที่วัตถุเหล่านั้นอาจมีโดยเฉพาะประเภทใดเช่นวัตถุที่กำหนดเอง tuples รายการ dicts และชุดประกอบด้วย พวกเขาสามารถมีอินสแตนซ์ซึ่งกันและกันรวมทั้งตัวเลขสตริงและวัตถุอื่น ๆ
คำตอบที่สมบูรณ์มากขึ้น
การใช้ 64 บิต Python 3.6 จากการกระจาย Anaconda ด้วย sys.getsizeof ฉันได้กำหนดขนาดต่ำสุดของวัตถุต่อไปนี้และโปรดทราบว่าชุดและกำหนดพื้นที่ preicocate preicocate เพื่อให้ว่างเปล่าไม่เติบโตอีกจนกว่าจะถึงจำนวนที่กำหนด (ซึ่งอาจ แตกต่างกันไปตามการใช้งานของภาษา):
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
) ในนิยามคลาส
อินสแตนซ์แบบ Slotted เริ่มต้นด้วย 48 ไบต์ในองค์ประกอบแรกของพวกเขาและเพิ่มขึ้น 8 แต่ละเพิ่มเติม วัตถุ slotted ที่ว่างเปล่าเท่านั้นที่มีขนาด 16 ไบต์และอินสแตนซ์ที่ไม่มีข้อมูลทำให้เข้าใจได้น้อยมาก
นอกจากนี้ยังมีฟังก์ชั่นความหมายแต่ละคนมีวัตถุรหัสอาจ docstrings และคุณลักษณะที่เป็นไปได้อื่น ๆ __dict__
แม้กระทั่ง
นอกจากนี้โปรดทราบว่าเราใช้sys.getsizeof()
เพราะเราใส่ใจเกี่ยวกับการใช้พื้นที่ส่วนเพิ่มซึ่งรวมถึงค่าใช้จ่ายในการรวบรวมขยะสำหรับวัตถุจากเอกสาร :
getsizeof () เรียกใช้__sizeof__
เมธอดของวัตถุและเพิ่มโอเวอร์เฮดตัวรวบรวมขยะเพิ่มเติมหากอ็อบเจ็กต์ได้รับการจัดการโดยตัวรวบรวมขยะ
นอกจากนี้โปรดทราบว่าการปรับขนาดรายการ (เช่นการต่อท้ายรายการซ้ำ) ทำให้รายการจัดสรรพื้นที่ล่วงหน้าคล้ายกับชุดและ dicts จาก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);
ข้อมูลทางประวัติศาสตร์
การวิเคราะห์ Python 2.7 ยืนยันด้วยguppy.hpy
และ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 ไบต์เหล่านั้นชี้ไปยังสถานที่ในหน่วยความจำที่มีรายการนั้นอยู่ที่ 4 ไบต์เป็นความกว้างคงที่สำหรับ Unicode ใน Python 2 ถ้าฉันจำได้อย่างถูกต้อง แต่ใน Python 3 str กลายเป็น Unicode ของความกว้างเท่ากับความกว้างสูงสุดของอักขระ
(และสำหรับช่องเพิ่มเติมดูคำตอบนี้ )
ฟังก์ชั่นที่สมบูรณ์ยิ่งขึ้น
เราต้องการฟังก์ชั่นที่ค้นหาองค์ประกอบในรายการสิ่งอันดับชุด dicts 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
.) อย่างไรก็ตามมาตรการนี้จะขยายขอบเขตมากขึ้นกว่าที่เราตั้งใจไว้หากเราไม่ระมัดระวัง
ตัวอย่างเช่นฟังก์ชั่นรู้มากเกี่ยวกับโมดูลที่สร้างขึ้น
จุดแตกต่างอีกอย่างหนึ่งก็คือสตริงที่เป็นกุญแจในพจนานุกรมมักจะถูกฝึกงานดังนั้นจึงไม่ได้ทำซ้ำ การตรวจสอบid(key)
จะทำให้เราสามารถหลีกเลี่ยงการนับซ้ำซึ่งเราทำในส่วนถัดไป โซลูชันบัญชีดำข้ามคีย์การนับที่เป็นสตริงโดยสิ้นเชิง
ประเภทที่อนุญาต, ผู้เยี่ยมชมซ้ำ (การใช้งานแบบเก่า)
เพื่อครอบคลุมประเภทเหล่านี้ส่วนใหญ่แทนที่จะอาศัยโมดูล gc ฉันได้เขียนฟังก์ชันเรียกซ้ำนี้เพื่อพยายามประเมินขนาดของวัตถุ Python ส่วนใหญ่รวมถึง builtins ส่วนใหญ่ประเภทในโมดูลการรวบรวมและประเภทที่กำหนดเอง (slotted และอื่น ๆ ) .
ฟังก์ชั่นประเภทนี้ให้การควบคุมที่ละเอียดยิ่งกว่าประเภทที่เราจะนับสำหรับการใช้หน่วยความจำ แต่มีอันตรายจากการทิ้งประเภท:
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)
และฉันทดสอบมันค่อนข้างตั้งใจ (ฉันควร unittest):
>>> 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
การนำไปใช้งานนี้แบ่งย่อยตามคำจำกัดความของคลาสและนิยามฟังก์ชันเนื่องจากเราไม่ได้ดำเนินการตามคุณลักษณะทั้งหมดของพวกเขา แต่เนื่องจากพวกเขาควรมีอยู่เพียงครั้งเดียวในหน่วยความจำสำหรับกระบวนการขนาดของพวกเขาไม่สำคัญมากนัก