-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Object allocation overallocates by 8 bytes for instances of tuple subclasses #100659
Comments
Here's an ad-hoc reproducer (not exactly a proof of the overallocation, but at least strong evidence), on main. Let's start with a 3-element namedtuple called Python 3.12.0a3+ (heads/main:d52d4942cf, Jan 1 2023, 14:45:48) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from collections import namedtuple, Counter
>>> Point = namedtuple("Point", "x y z")
>>> import sys
>>> sys.getsizeof(Point(1, 2, 3))
64
>>> Point.__basicsize__, Point.__itemsize__
(24, 8) But if we create a large number of >>> points = [Point(1, 2, 3) for _ in range(10**6)]
>>> ids = sorted(map(id, points))
>>> Counter(id1 - id0 for id1, id0 in zip(ids[1:], ids))
Counter({80: 995084, 144: 4823, 16528: 77, 480: 2, 160: 1, 184848: 1, 880: 1, 960: 1, 1360: 1, 800: 1, 2720: 1, 720: 1, 904640: 1, 207072: 1, 6640: 1, 180368: 1, 82064: 1}) |
Similar results here for main and 3.11.1, macos M1 namedtuple
sys.getsizeof: 64
[(80, 995094), (144, 4900), (160, 1), (3200, 1), (82064, 1), (136896, 1), (7585936, 1)] Using tuple
sys.getsizeof: 64
[(64, 996067), (128, 3918), (192, 2), (576, 2), (768, 1), (832, 1), (1024, 1), (1536, 1), (6144, 1), (7808, 1), (16832, 1), (49280, 1), (82048, 1), (7585920, 1)] Subclasses of class UserInt(int):
__slots__ = ()
UserInt(10**50) user-int
sys.getsizeof: 64
[(64, 996055), (128, 3924), (192, 3), (256, 1), (320, 1), (384, 1), (448, 2), (896, 1), (1088, 1), (1344, 1), (1664, 1), (2240, 1), (2880, 1), (7360, 1), (16512, 1), (51264, 1), (66304, 1), (1127040, 1), (7700608, 1)] class UserDict(dict):
__slots__ = ()
UserDict(x=10) user-dict
basicsize: 48
basicsize (+ gc): 64
[(64, 996055), (128, 8), (192, 1), (256, 1), (320, 1), (448, 2), (640, 1), (896, 1), (1664, 1), (1728, 1), (2240, 1), (3328, 1), (7360, 1), (32896, 3888), (49280, 32), (51264, 1), (66304, 1), (1127040, 1), (7733376, 1)] |
This issue looks relevant, we might need to investigate which case could be reduced. |
@corona10 Thanks; that looks like the exact same issue. I'll close this as a duplicate. |
Given a tuple subclass defined by
class mytuple(tuple): pass
, memory allocation formytuple
objects currently goes through this code:cpython/Objects/typeobject.c
Lines 1292 to 1293 in e83f88a
That
+1
innitems+1
has a couple of consequences formytuple
instances; the first of those is a possible perfomance opportunity, while the second is a minor bug.sys.getsizeof
consistently under-reports for these instances: the value reported is 8 bytes smaller than the value that was passed toPyObject_Malloc
during object allocation(Note: byte counts above assume a 64-bit platform.)
I did some hacky experimentation and was able to remove the
+1
specifically for tuple subclasses with no apparent ill-effects. But the+1
definitely is needed for some varobjects (includingPyHeapTypeObject
, I think), and there may also be 3rd party extension code that either deliberately or inadvertently relies on it.I haven't investigated whether there are other varobjects besides tuple subclasses for which the
+1
could be removed.The text was updated successfully, but these errors were encountered: