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 formytupleobjects currently goes through this code:cpython/Objects/typeobject.c
Lines 1292 to 1293 in e83f88a
That
+1innitems+1has a couple of consequences formytupleinstances; the first of those is a possible perfomance opportunity, while the second is a minor bug.sys.getsizeofconsistently under-reports for these instances: the value reported is 8 bytes smaller than the value that was passed toPyObject_Mallocduring object allocation(Note: byte counts above assume a 64-bit platform.)
I did some hacky experimentation and was able to remove the
+1specifically for tuple subclasses with no apparent ill-effects. But the+1definitely 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
+1could be removed.The text was updated successfully, but these errors were encountered: