-
-
Notifications
You must be signed in to change notification settings - Fork 31.5k
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
bpo-44914: Maintain invariants of type version tags. #27773
bpo-44914: Maintain invariants of type version tags. #27773
Conversation
🤖 New build scheduled with the buildbot fleet by @markshannon for commit 68fd56e 🤖 If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again. |
LGTM. If you want tests, I opened another PR at https://github.com/python/cpython/pull/27774/files for your consideration please (thought it also has my alternative fix in there). |
entry->value = NULL; | ||
} | ||
|
||
// Mark all version tags as invalid | ||
PyType_Modified(&PyBaseObject_Type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm confused as to why we don't need this anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we don't reuse the version numbers anymore, so the old ones remain valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW: test_descr
is now passing on my LOAD_METHOD
specialization branch (without requiring any type identity checks :). Woohoo!
🤖 New build scheduled with the buildbot fleet by @markshannon for commit 3850ef2 🤖 If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again. |
🤖 New build scheduled with the buildbot fleet by @markshannon for commit 68da204 🤖 If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a NEWS entry?
@@ -45,7 +45,7 @@ class object "PyObject *" "&PyBaseObject_Type" | |||
|
|||
// bpo-42745: next_version_tag remains shared by all interpreters because of static types | |||
// Used to set PyTypeObject.tp_version_tag | |||
static unsigned int next_version_tag = 0; | |||
static unsigned int next_version_tag = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worth it to change PyTypeObject.tp_version_tag type to uint64_t to reduce the risk of overflow?
2^64 version changes is less likely than 2^32 changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyType_ClearCache is documented to return unsigned int
, not uint64_t
, so I don't know if we can change that without deprecation. https://docs.python.org/3/c-api/type.html#c.PyType_ClearCache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
64 bit versions would make the type cache twice the size and might well make performance worse.
Classes don't change that much. If it happens that long running programs use 2**32 versions, then it is likely that a small set of classes change often. In that case we could limit the number of version that an individual class can use up.
// Wrap-around or just starting Python - clear the whole cache | ||
type_cache_clear(cache, 1); | ||
if (next_version_tag == 0) { | ||
/* We have run out of version numbers */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please elaborate the comment to explain what happen on next_version_tag overflow? I understand that old types keep their caches, but types which are are changed are no longer cached.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random thought which I haven't verified:
Why not call PyType_Modified
on base object type and clear cache on overflow, then continue incrementing version tag counter again?
That way even after overflow, new version tags are assigned. Just that every UINT32_TMAX
type cache miss event will cause temporarily bad performance. But that seems worth it vs bad performance all the way after we hit UINT32_TMAX
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please elaborate the comment to explain what happen on next_version_tag overflow? I understand that old types keep their caches, but types which are are changed are no longer cached.
It doesn't overflow. Once next_version_tag
reaches 0, we stop issuing new versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random thought which I haven't verified:
Why not callPyType_Modified
on base object type and clear cache on overflow, then continue incrementing version tag counter again?That way even after overflow, new version tags are assigned. Just that every
UINT32_TMAX
type cache miss event will cause temporarily bad performance. But that seems worth it vs bad performance all the way after we hitUINT32_TMAX
.
We would also need to find and clear every specialized instruction that uses the version tag. It is possible, but I don't think it is worth it. 2**32 is a large number.
Objects/typeobject.c
Outdated
entry->version = 0; | ||
Py_CLEAR(entry->name); | ||
entry->value = NULL; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not reusing type_cache_clear() with a parameter to decide how to handle entry->name, to factorize the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because type_cache_clear()
does something different. It resets the cache to its starting state.
This code clears it out during finalization.
Any function with a boolean argument that changes it behavior is really two functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've refactored type_cache_clear()
to take a PyObject *
.
165ad11
to
6bbb1cf
Compare
Would it make sense to emit a warning once Python cannot issue new version? |
I don't think we should worry about it. In the highly unlikely case that it happens in practice, we can reconsider. |
else { | ||
Py_CLEAR(entry->name); | ||
} | ||
Py_XSETREF(entry->name, _Py_XNewRef(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remark: _Py_XNewRef() should not be used, you should use the public Py_XNewRef() instead.
Summary: This is a backport of python/cpython#27773 and python/cpython#27774 from CPython 3.12. They allow us to perform lookups that assign a new valid version tag to a type in callbacks invoked by `PyType_Lookup()`, which I need to do in D42512608 (and probably others in the future). I moved the call to `_PyClassLoader_ClearCache()` from `_PyType_ClearCache()` to `type_cache_clear()` to ensure that it's called by both `_PyType_Fini()` and `PyType_ClearCache()` (the upstream changes made it so `_PyType_Fini()` no longer calls `_PyType_ClearCache()`, instead calling `type_cache_clear()` directly). Original Summaries: bpo-44914: Maintain invariants of type version tags. (GH-27773) * Do not invalidate type versions unnecessarily. ---- bpo-44914: Add tests for some invariants of tp_version_tag (GH-27774) Reviewed By: carljm Differential Revision: D42760412 fbshipit-source-id: 9d5f2a5
https://bugs.python.org/issue44914