-
-
Notifications
You must be signed in to change notification settings - Fork 34.8k
gh-153198 Optimize set and frozenset membership lookup #153199
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
d25015b
81f2fa7
110770e
70dbe4c
0d3823d
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 @@ | ||
| Optimize set and frozenset membership lookup by inlining ``set_do_lookup`` and adding a ``PyUnicode_CheckExact`` fast path to ``set_compare_frozenset``. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -166,8 +166,7 @@ set_compare_entry_lock_held(PySetObject *so, setentry *table, setentry *entry, | |
|
|
||
| // This is similar to set_compare_entry_lock_held() but we don't need to | ||
| // incref startkey before comparing and we don't need to check if the set has | ||
| // changed. This also omits the PyUnicode_CheckExact() special case since it | ||
| // doesn't help much for frozensets. | ||
| // changed. | ||
| static inline Py_ALWAYS_INLINE int | ||
| set_compare_frozenset(PySetObject *so, setentry *table, setentry *ep, | ||
| PyObject *key, Py_hash_t hash) | ||
|
|
@@ -182,6 +181,11 @@ set_compare_frozenset(PySetObject *so, setentry *table, setentry *ep, | |
| } | ||
| Py_ssize_t ep_hash = ep->hash; | ||
| if (ep_hash == hash) { | ||
| if (PyUnicode_CheckExact(startkey) | ||
| && PyUnicode_CheckExact(key) | ||
| && unicode_eq(startkey, key)) { | ||
| return SET_LOOKKEY_FOUND; | ||
| } | ||
| int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); | ||
| if (cmp < 0) { | ||
| return SET_LOOKKEY_ERROR; | ||
|
|
@@ -217,7 +221,7 @@ set_zero_table(setentry *table, size_t size) | |
| /* This must be >= 1 */ | ||
| #define PERTURB_SHIFT 5 | ||
|
|
||
| static int | ||
| static inline Py_ALWAYS_INLINE int | ||
|
Member
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. Is the compiler not already specialising this?
Author
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. Below is a response from an AI agent: Without explicitly using 1. The Heuristics Threshold (Function Size)Modern C compilers (GCC, Clang, MSVC) use conservative heuristics to decide what to inline. They estimate the number of instructions in a function, and if it exceeds a certain threshold, they won't inline it to avoid bloating the binary. Because set_do_lookup contains:
It is too large for compilers to automatically inline under default heuristics. 2. Compilers Cannot Devirtualize Pointers in Standalone FunctionsIf a function is not inlined, it must be compiled as a separate, general-purpose function. In that compiled code: status = compare_entry(so, table, entry, key, hash);
3. Even with LTO + PGO, Auto-Specialization is Not GuaranteedWhile Link-Time Optimization (LTO) and Profile-Guided Optimization (PGO) allow the compiler to see across translation units and profile hot paths:
Member
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.
See our AI Policy, we expect PR authors and those filing issues to be able to explain their proposed changes in their own words.
Author
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. Mentioning
Member
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. Yes, it's not guaranteed, but modern compilers are pretty smart. Do new compilers already inline it? |
||
| set_do_lookup(PySetObject *so, setentry *table, size_t mask, PyObject *key, | ||
| Py_hash_t hash, setentry **epp, compare_func compare_entry) | ||
| { | ||
|
|
||
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.
question: why did you decide to use
stras the fast path items here? what is so special about it?