-
Notifications
You must be signed in to change notification settings - Fork 50
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
Compact layout of Plain Python Objects #72
Comments
I sense a connection to the idea of Hidden Classes (from the JavaScript world, esp. v8). |
Hidden classes (originally known as "maps" Chambers, 92 sect 6.1.1) have a couple of important disadvantages.
[1] Suppose we want to specialize for attribute lookup on an instance. Suppose our prototype object belongs to set |
There are two things we need to do to get close to the ideal layout.
Where Note that an object's dictionary keys is not the same as the set of keys present in the dictionary. E.g. the dict
or as
The later form may be more efficient if it allows the keys |
By putting the values directly after the Compact layoutAfter accessing the
|
Instead of using some First of all, a couple of observations:
The idea is to pre-allocate the shared dict keys in the class with capacity of 20 (or whatever is the capacity for an allocation of 32) and set an initial size to that same value. For each new object, after the first, shrink the size of the values until it reaches Invariants that must be enforced for each object.
|
Mark can speak for himself, but to me the disadvantage of that is the need
to allocate two blocks of memory instead of one for the values, and the
extra pointer deref to get at the values in a specialized version of
LOAD_ATTR. (And the fact that the guard in the specialized opcode would
have to check that the values pointer is still there.)
It's true that that approach is less complex to code, though.
|
As an intermediate step, @methane's suggestion makes sense. However, in the longer term we will want the compact layout described above. See also #30 (comment) which will allow values to map from a subset of the keys. |
The layout we now have (since python/cpython#28802) is quite good and we should probably spend our efforts on making sure that almost all objects use the compact layout and making sure that the C API supports compact layout, rather than trying to make the layout even more compact. So, I'm deferring this issue for now. I'm deferring this rather than closing it, as I still think we will want to embed the values into the object eventually. Although, that may not happen until 3.12 or even later. |
Definitely not 3.12, but maybe 3.13. @carljm Would the Cinder team be interested in pursuing this? |
In principle, yes -- I think it's now acquired permanent residency on our long-term roadmap of things to look at :) In practice, it's not near the top of the short-term roadmap, and I think it's unlikely that we would prioritize it before we upgrade to 3.12 and can experiment with it relative to the already-done dict-values stuff, which probably means 2024 at best. So maybe/yes, but not soon. cc @mpage as someone who has generally been interested in this topic, and @swtaarrs as the person currently managing the Cinder perf roadmap |
I just ran into this in "real life", note the idea in shapes/hidden classes is to learn the shape of objects which allows us to have both compact layout in practice (e.g. if I have a small integer member - it'll take just 32 bytes of memory rather than an object) and inline caches (the shape information is stored on the functions themselves which makes property access (like obj.x) behave like a memory offset "as fast as C". This proposal doesn't actually address that? |
CPython 3.11+ has inline caching, and it already does make use of the learned object shape information to make attribute accesses fast. Not everything is described in a single discussion topic :) You may find it interesting to look at At this point, this topic is specifically about the remaining step of moving the instance attribute values array inline into the object itself, rather than in a separate block of memory. |
It only took 2 and a half years, but this is done 🎉 |
Plain Python Objects are, at the Python level, just a thin wrapper around a dictionary.
However, they are generally used as objects with common behavior across all objects of a class.
Consider the much overused example of
Color
:This object has 3 words of data, but for a header size of
H
takes a huge(H+1)+(H+4)+5 == 2H+10
words assuming shared keys. Without shared keys it takes an even worse(H+1)+(H+4)+13+5 == 2H+23
words.We can make two simple observations.
Which suggests two broad strategies:
self.attr
accesses in a class's methods are visible to the compiler.Ideally we would like to use
H+3
words of memory, but evenH+5
(allowing 2 slots for internal use or over allocation) would halve memory use.The text was updated successfully, but these errors were encountered: