python / python/cpython

Objects with both `__slots__` and `__dict__` have much larger size than needed (up to 4x) on Python 3.13

Ouverte
#135,385 4 commentaires 1 réaction 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

3.13 3.14 interpreter-core performance type-bug
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

Description

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

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par les modifications de la disposition des objets décrites dans Objects/object_layout.md et reproduisez le problème avec les classes slots/dict fournies à l’aide de tracemalloc sur Python 3.13 et 3.12. Examinez les PRs liés gh-135389 et gh-156656, puis vérifiez que les instances mixtes slots-and-dict n’entraînent plus la surconsommation de mémoire signalée, tout en préservant les dispositions démontrées.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
backend
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.