From 146f5aa1adb722b28a1a206fc4fc77a29dd28f1a Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 12 Jul 2025 22:21:35 +0200 Subject: [PATCH 1/9] Add test for long_hash --- Lib/test/test_long.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index f336d49fa4f008..e80fd74563be6e 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1693,5 +1693,8 @@ class MyInt(int): # GH-117195 -- This shouldn't crash object.__sizeof__(1) + def test_long_hash(self): + assert hash(-2**61) != -1 + if __name__ == "__main__": unittest.main() From a162da25ba66e8f8bd50f68e95cf6938c397b670 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 12 Jul 2025 22:22:55 +0200 Subject: [PATCH 2/9] Unroll first digit calculation in long_hash --- Objects/longobject.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 581db10b54ab57..b779b0489151d5 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3676,7 +3676,13 @@ long_hash(PyObject *obj) } i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = 0; + + // unroll first two digits + assert(i>=2); + --i; + x = v->long_value.ob_digit[i]; + assert(x < _PyHASH_MODULUS); + while (--i >= 0) { /* Here x is a quantity in the range [0, _PyHASH_MODULUS); we want to compute x * 2**PyLong_SHIFT + v->long_value.ob_digit[i] modulo From 4f9fc76d4cc0a5468a91b229744d420dd6057dfa Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 12 Jul 2025 22:23:09 +0200 Subject: [PATCH 3/9] Unroll second digit calculation in long_hash --- Objects/longobject.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Objects/longobject.c b/Objects/longobject.c index b779b0489151d5..4e9341b600977a 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3682,6 +3682,10 @@ long_hash(PyObject *obj) --i; x = v->long_value.ob_digit[i]; assert(x < _PyHASH_MODULUS); + --i; + x = ((x << PyLong_SHIFT)); + x += v->long_value.ob_digit[i]; + assert(x < _PyHASH_MODULUS); while (--i >= 0) { /* Here x is a quantity in the range [0, _PyHASH_MODULUS); we From 07bce4b8187751b6fdff661e91e7b0d5eb6693f5 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 12 Jul 2025 23:37:40 +0200 Subject: [PATCH 4/9] whitespace --- Lib/test/test_long.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index e80fd74563be6e..371cd2759b1e09 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1695,6 +1695,6 @@ class MyInt(int): def test_long_hash(self): assert hash(-2**61) != -1 - + if __name__ == "__main__": unittest.main() From 32341dea79325cb7486678c7a597f048cee796d6 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 12 Jul 2025 23:40:34 +0200 Subject: [PATCH 5/9] add gh number --- Lib/test/test_long.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index e80fd74563be6e..2ce17fbc48af20 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1694,7 +1694,10 @@ class MyInt(int): object.__sizeof__(1) def test_long_hash(self): + # gh-136599 + assert hash(10) == 10 + assert hash(-1) == -2 assert hash(-2**61) != -1 - + if __name__ == "__main__": unittest.main() From a48860f59129dbd9ae301eb143c5c17697cec5a0 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 13 Jul 2025 20:58:27 +0200 Subject: [PATCH 6/9] review comments --- Lib/test/test_long.py | 11 +++++++---- Objects/longobject.c | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 2ce17fbc48af20..084a669de1b6be 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1693,11 +1693,14 @@ class MyInt(int): # GH-117195 -- This shouldn't crash object.__sizeof__(1) - def test_long_hash(self): + def test_hash(self): # gh-136599 - assert hash(10) == 10 - assert hash(-1) == -2 - assert hash(-2**61) != -1 + self.assertEqual(hash(-1), -2) + self.assertEqual(hash(10), 10) + self.assertEqual(hash(2**31 -1), 2**31 - 1) + self.assertNotEqual(hash(-2**31), -1) + self.assertNotEqual(hash(-2**61), -1) + if __name__ == "__main__": unittest.main() diff --git a/Objects/longobject.c b/Objects/longobject.c index 4e9341b600977a..94c99ce9e78024 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3682,11 +3682,12 @@ long_hash(PyObject *obj) --i; x = v->long_value.ob_digit[i]; assert(x < _PyHASH_MODULUS); +#if ( PyHASH_BITS > (2*PyLong_SHIFT)) --i; x = ((x << PyLong_SHIFT)); x += v->long_value.ob_digit[i]; assert(x < _PyHASH_MODULUS); - +#endif while (--i >= 0) { /* Here x is a quantity in the range [0, _PyHASH_MODULUS); we want to compute x * 2**PyLong_SHIFT + v->long_value.ob_digit[i] modulo From 6d3754baeecb714833904ce561d382dcf93fc741 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 13 Jul 2025 21:18:52 +0200 Subject: [PATCH 7/9] fix test on wasi --- Lib/test/test_long.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 084a669de1b6be..0173ebd5a3e1bc 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1697,7 +1697,7 @@ def test_hash(self): # gh-136599 self.assertEqual(hash(-1), -2) self.assertEqual(hash(10), 10) - self.assertEqual(hash(2**31 -1), 2**31 - 1) + self.assertEqual(hash(2**31 - 2), 2**31 - 2) self.assertNotEqual(hash(-2**31), -1) self.assertNotEqual(hash(-2**61), -1) From fec9fbe6bfa9fa2b096914438e27c485bdf5c302 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 13 Jul 2025 21:21:25 +0200 Subject: [PATCH 8/9] add news entry --- .../2025-07-13-21-21-17.gh-issue-136599.sLhm2O.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-07-13-21-21-17.gh-issue-136599.sLhm2O.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-13-21-21-17.gh-issue-136599.sLhm2O.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-13-21-21-17.gh-issue-136599.sLhm2O.rst new file mode 100644 index 00000000000000..9bcb13f9e20caf --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-13-21-21-17.gh-issue-136599.sLhm2O.rst @@ -0,0 +1 @@ +Improve performance of :class:`int` hash calculations. From 08d7ba93e78d88ec3b8edab77ef2553c26b4417e Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 25 Jul 2025 21:13:05 +0200 Subject: [PATCH 9/9] review comments --- Lib/test/test_long.py | 14 +++++++++++--- Objects/longobject.c | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 0173ebd5a3e1bc..c206c9e02a9fbc 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1696,10 +1696,18 @@ class MyInt(int): def test_hash(self): # gh-136599 self.assertEqual(hash(-1), -2) + self.assertEqual(hash(0), 0) self.assertEqual(hash(10), 10) - self.assertEqual(hash(2**31 - 2), 2**31 - 2) - self.assertNotEqual(hash(-2**31), -1) - self.assertNotEqual(hash(-2**61), -1) + + self.assertEqual(hash(sys.hash_info.modulus - 2), sys.hash_info.modulus - 2) + self.assertEqual(hash(sys.hash_info.modulus - 1), sys.hash_info.modulus - 1) + self.assertEqual(hash(sys.hash_info.modulus), 0) + self.assertEqual(hash(sys.hash_info.modulus + 1), 1) + + self.assertEqual(hash(-sys.hash_info.modulus - 2), -2) + self.assertEqual(hash(-sys.hash_info.modulus - 1), -2) + self.assertEqual(hash(-sys.hash_info.modulus), 0) + self.assertEqual(hash(-sys.hash_info.modulus + 1), - (sys.hash_info.modulus - 1)) if __name__ == "__main__": diff --git a/Objects/longobject.c b/Objects/longobject.c index 94c99ce9e78024..e5e36cdb01b0eb 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3678,11 +3678,13 @@ long_hash(PyObject *obj) sign = _PyLong_NonCompactSign(v); // unroll first two digits +#if ( PyHASH_BITS > PyLong_SHIFT ) assert(i>=2); --i; x = v->long_value.ob_digit[i]; assert(x < _PyHASH_MODULUS); -#if ( PyHASH_BITS > (2*PyLong_SHIFT)) +#endif +#if ( PyHASH_BITS > (2 * PyLong_SHIFT) ) --i; x = ((x << PyLong_SHIFT)); x += v->long_value.ob_digit[i];