-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
bpo-44914: Maintain invariants of type version tags. #27773
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
Changes from all commits
f49a1ce
68fd56e
3850ef2
68da204
4e6865a
6bbb1cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Class version tags are no longer recycled. | ||
|
||
This means that a version tag serves as a unique identifier for the state of | ||
a class. We rely on this for effective specialization of the LOAD_ATTR and | ||
other instructions. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
||
typedef struct PySlot_Offset { | ||
short subslot_offset; | ||
|
@@ -233,24 +233,14 @@ get_type_cache(void) | |
|
||
|
||
static void | ||
type_cache_clear(struct type_cache *cache, int use_none) | ||
type_cache_clear(struct type_cache *cache, PyObject *value) | ||
{ | ||
for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) { | ||
struct type_cache_entry *entry = &cache->hashtable[i]; | ||
entry->version = 0; | ||
if (use_none) { | ||
// Set to None so _PyType_Lookup() can use Py_SETREF(), | ||
// rather than using slower Py_XSETREF(). | ||
Py_XSETREF(entry->name, Py_NewRef(Py_None)); | ||
} | ||
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 commentThe 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. |
||
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 commentThe 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 commentThe 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. |
||
} | ||
|
||
|
||
|
@@ -287,14 +277,11 @@ _PyType_ClearCache(PyInterpreterState *interp) | |
sizeof(cache->hashtable) / 1024); | ||
#endif | ||
|
||
unsigned int cur_version_tag = next_version_tag - 1; | ||
if (_Py_IsMainInterpreter(interp)) { | ||
next_version_tag = 0; | ||
} | ||
// Set to None, rather than NULL, so _PyType_Lookup() can | ||
// use Py_SETREF() rather than using slower Py_XSETREF(). | ||
type_cache_clear(cache, Py_None); | ||
|
||
type_cache_clear(cache, 0); | ||
|
||
return cur_version_tag; | ||
return next_version_tag - 1; | ||
} | ||
|
||
|
||
|
@@ -309,7 +296,8 @@ PyType_ClearCache(void) | |
void | ||
_PyType_Fini(PyInterpreterState *interp) | ||
{ | ||
_PyType_ClearCache(interp); | ||
struct type_cache *cache = &interp->type_cache; | ||
type_cache_clear(cache, NULL); | ||
if (_Py_IsMainInterpreter(interp)) { | ||
clear_slotdefs(); | ||
} | ||
|
@@ -426,14 +414,12 @@ assign_version_tag(struct type_cache *cache, PyTypeObject *type) | |
if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) | ||
return 0; | ||
|
||
type->tp_version_tag = next_version_tag++; | ||
/* for stress-testing: next_version_tag &= 0xFF; */ | ||
|
||
if (type->tp_version_tag == 0) { | ||
// 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Random thought which I haven't verified: That way even after overflow, new version tags are assigned. Just that every There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It doesn't overflow. Once There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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. |
||
return 0; | ||
} | ||
type->tp_version_tag = next_version_tag++; | ||
assert (type->tp_version_tag != 0); | ||
|
||
bases = type->tp_bases; | ||
n = PyTuple_GET_SIZE(bases); | ||
|
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.
Uh oh!
There was an error while loading. Please reload this page.
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
, notuint64_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.