GH-137759:Limit _PyObject_HashFast to dicts keys, rename it, and mark it as Py_ALWAYS_INLINE#137828
GH-137759:Limit _PyObject_HashFast to dicts keys, rename it, and mark it as Py_ALWAYS_INLINE#137828JasonMendoza2008 wants to merge 3 commits intopython:mainfrom
Conversation
…d mark it as Py_ALWAYS_INLINE
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
I'm not totally convinced that this is true. Detecting the fast-path for strings is just one pointer comparison, which really won't have a noticeable performance impact. I agree that we want |
I'm fine with adding a fast-path for strings and integers (but then do we even need Footnotes
|
Yeah, because even if there's no speedup there, it's just simpler maintenance (in the future, we might want to make other assumptions about a dictionary key's hash that improve performance). |
Playing devil's advocate: if we don't detect it, we're a a function pointer dereference and call away from getting the hash anyway, right? The hash is not recomputed without that fast-path since it's stored (for strings, but for integers it's just an AND operation if I remember correctly)? |
|
A function pointer call has a lot more to do than just some reads, but maybe the overhead isn't noticeable? |
The best thing to answer this without guessing is probably to compile both (or all three, if we also include a fast path for integers) and run pyperformance. |
|
The
I dislike this macro, I prefer to rely on the compiler to decide if a function should be inlined or not. The compiler is smart enough to decide if a function should be inlined depending on the pressure on register allocation and other stuffs. |
|
I tried to remove the optimization for diff --git a/Objects/setobject.c b/Objects/setobject.c
index 85f4d7d4031..5c8139a7c5f 100644
--- a/Objects/setobject.c
+++ b/Objects/setobject.c
@@ -409,7 +409,7 @@ set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t hash)
static int
set_add_key(PySetObject *so, PyObject *key)
{
- Py_hash_t hash = _PyObject_HashFast(key);
+ Py_hash_t hash = PyObject_Hash(key);
if (hash == -1) {
set_unhashable_type(key);
return -1;Results on Linux with CPU isolation and Python built with
So yes, Benchmark script: import pyperf
runner = pyperf.Runner()
runner.timeit('set(range(256))',
setup='data = tuple(range(256)); Set=set',
stmt='Set(data)')
runner.timeit('set(map(chr, range(256)))',
setup='data = tuple(map(chr, range(256))); Set=set',
stmt='Set(data)') |
Nevermind I now realize I misunderstood what you meant by leaving the code as it is. Leaving the fast path everywhere and fixing where it was forgotten seem fine to me but other people (cf. issue) seemed to disagree so I'll let you guys decide. |
|
Using |
|
This PR is stale because it has been open for 30 days with no activity. |
Should someone that has the authority to do so do that? Not sure how to move forward this PR. |


Context:
setobject.cused a mix of_PyObject_HashFastandPyObject_Hash. The consensus1 was that_PyObject_HashFastshould only really be used for dict keys (string-only sets are not so omnipresent). This PR uses the standardPyObject_Hashwithout the fast path (makingset[Any]faster andset[str]slower2), renames_PyObject_HashFastsince its name was misleading and marked it asPy_ALWAYS_INLINEto force inlining.Pretty sure this doesn't require a NEWS entry?
set_intersectionuse_PyObject_HashFast()? #137759Footnotes
There were discussions in the same issue around removing
_PyObject_HashFastcompletely and adding the fast path for strings (and even potentially integers) toPyObject_Hashbut there was no consensus so I'm not including it for now. ↩In this comment , I run
pyperformanceon two versions ofsetobject.c, one using HashFast everywhere and one using the standard Hash everywhere. None of these correspond to the current implementation ofsetobject.csince the current implementation uses a mix of both. Obviously in all cases, dictionaries use HashFast. ↩