Objects with both `__slots__` and `__dict__` have much larger size than needed (up to 4x) on Python 3.13
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 77.2k
- Fork
- 36k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
Bug report
Bug description:
On Python 3.13, instances that have both __slots__ and __dict__ defined (e.g. through inheritance)
can have a size that is more than four times of that in previous Python versions (e.g. 3.12).
import tracemalloc
import gc
class _Point2D:
__slots__ = ("x", "y")
class Point3D_OnlySlots(_Point2D):
__slots__ = ("z",)
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
class Point3D_DictAndSlots(_Point2D):
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
class Point3D_OnlyDict:
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
gc.collect() # clear freelists
tracemalloc.start()
_ = [Point3D_OnlySlots(1, 2, 3) for _ in range(1_000_000)]
print(
f"1M Point3D (only __slots__) instances: {tracemalloc.get_traced_memory()[0]:_} bytes"
)
gc.collect() # clear freelists
tracemalloc.start()
_ = [Point3D_OnlyDict(1, 2, 3) for _ in range(1_000_000)]
print(
f"1M Point3D (only __dict__) instances: {tracemalloc.get_traced_memory()[0]:_} bytes"
)
gc.collect() # clear freelists
tracemalloc.start()
_ = [Point3D_DictAndSlots(1, 2, 3) for _ in range(1_000_000)]
print(
f"1M Point3D (__dict__ and __slots__) instances: {tracemalloc.get_traced_memory()[0]:_} bytes"
)
On python 3.13.3, this prints:
1M Point3D (only __slots__) instances: 64_448_792 bytes
1M Point3D (only __dict__) instances: 104_451_928 bytes
1M Point3D (__dict__ and __slots__) instances: 416_448_848 bytes
On python 3.12.11, this prints:
1M Point3D (only __slots__) instances: 64_448_792 bytes
1M Point3D (only __dict__) instances: 96_451_808 bytes
1M Point3D (__dict__ and __slots__) instances: 96_452_232 bytes
The apparent cause seems to be the object layout changes (see here)
which introduced inline values. It appears that these don't mesh well with __slots__.
Could this be because the inline values need to have a fixed offset?
What appears to cause the dramatic increase above is that having (nonempty) __slots__ will
trigger __dict__ materialization (size 30) as soon as a non-slot attribute is set. In contrast to the optimized "no slots" case, this dict doesn't shrink as more instances are created.
Of course, mixing __slots__ and __dict__ is not recommended, but it's a trap that can be easily fallen into.
For example, if forgetting to define __slots__ anywhere in a complex class hierarchy.
I'm unsure if I'm understanding exactly what's going on though. @markshannon perhaps you can shed some light on this?
Related: #115776, #115822
CPython versions tested on:
3.13
Operating systems tested on:
macOS
Linked PRs
- gh-135389
- gh-156656
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu với các thay đổi về bố cục đối tượng được mô tả trong Objects/object_layout.md và tái hiện sự cố bằng các lớp slots/dict được cung cấp với tracemalloc trên Python 3.13 và 3.12. Xem xét các PR được liên kết gh-135389 và gh-156656, sau đó xác minh rằng các instance slots-and-dict kết hợp không còn phát sinh lượng bộ nhớ dư thừa đã được báo cáo, đồng thời vẫn giữ nguyên các bố cục được minh họa.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- backend
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 35/100