From 146f5aa1adb722b28a1a206fc4fc77a29dd28f1a Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 12 Jul 2025 22:21:35 +0200 Subject: [PATCH 01/16] 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 02/16] 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 03/16] 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 04/16] 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 05/16] 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 06/16] 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 07/16] 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 08/16] 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 09/16] 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]; From 76c4f6a57febcb1d5ac3ec865a2868bacce0c1ea Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 1 Sep 2025 07:06:06 +0200 Subject: [PATCH 10/16] Apply suggestions from code review Co-authored-by: Sergey B Kirpichev --- Objects/longobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 3118d2da5e980c..caac1a83fcb3c0 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3682,13 +3682,13 @@ long_hash(PyObject *obj) assert(i>=2); --i; x = v->long_value.ob_digit[i]; - assert(x < _PyHASH_MODULUS); + assert(x < PyHASH_MODULUS); #endif #if ( PyHASH_BITS > (2 * PyLong_SHIFT) ) --i; x = ((x << PyLong_SHIFT)); x += v->long_value.ob_digit[i]; - assert(x < _PyHASH_MODULUS); + assert(x < PyHASH_MODULUS); #endif while (--i >= 0) { /* Here x is a quantity in the range [0, _PyHASH_MODULUS); we From f720557f7e4515fdca977e305b602f07a2379935 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 1 Sep 2025 14:30:45 +0200 Subject: [PATCH 11/16] Update Objects/longobject.c Co-authored-by: Sergey B Kirpichev --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index caac1a83fcb3c0..7ad75868eb7f38 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3686,7 +3686,7 @@ long_hash(PyObject *obj) #endif #if ( PyHASH_BITS > (2 * PyLong_SHIFT) ) --i; - x = ((x << PyLong_SHIFT)); + x <<= PyLong_SHIFT; x += v->long_value.ob_digit[i]; assert(x < PyHASH_MODULUS); #endif From c1a318499cdb82a65513b16223dfa7e98b1dd074 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 2 Sep 2025 20:28:42 +0200 Subject: [PATCH 12/16] review comment --- Objects/longobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 8703c7ed4c5619..8b82482f753aa5 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3679,12 +3679,13 @@ long_hash(PyObject *obj) // unroll first digit Py_BUILD_ASSERT(PyHASH_BITS > PyLong_SHIFT); - assert(i>=2); + assert(i>=1); --i; x = v->long_value.ob_digit[i]; assert(x < PyHASH_MODULUS); // unroll second digit #if ( PyHASH_BITS > (2 * PyLong_SHIFT) ) + assert(i>=2); --i; x <<= PyLong_SHIFT; x += v->long_value.ob_digit[i]; From b9a487d6ada1bd3d49587abc89a0f16c58c3b4c6 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 3 Sep 2025 11:36:03 +0200 Subject: [PATCH 13/16] fix assert --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 8b82482f753aa5..73ff5ef7e59c0b 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3685,7 +3685,7 @@ long_hash(PyObject *obj) assert(x < PyHASH_MODULUS); // unroll second digit #if ( PyHASH_BITS > (2 * PyLong_SHIFT) ) - assert(i>=2); + assert(i>=1); --i; x <<= PyLong_SHIFT; x += v->long_value.ob_digit[i]; From b0fd0d8c9ef45c4b897087f3d6e97dd483ddb98e Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 3 Sep 2025 16:13:08 +0200 Subject: [PATCH 14/16] Update Objects/longobject.c Co-authored-by: Victor Stinner --- Objects/longobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 73ff5ef7e59c0b..0d3245e0aada60 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3683,8 +3683,9 @@ long_hash(PyObject *obj) --i; x = v->long_value.ob_digit[i]; assert(x < PyHASH_MODULUS); - // unroll second digit + #if ( PyHASH_BITS > (2 * PyLong_SHIFT) ) + // unroll second digit assert(i>=1); --i; x <<= PyLong_SHIFT; From c6e060daa8b51b8f2d1697cf952225e494ac0f76 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 3 Sep 2025 21:24:14 +0200 Subject: [PATCH 15/16] Apply suggestions from code review Co-authored-by: Serhiy Storchaka --- Objects/longobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 0d3245e0aada60..a5f12bc5e986ce 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3679,14 +3679,14 @@ long_hash(PyObject *obj) // unroll first digit Py_BUILD_ASSERT(PyHASH_BITS > PyLong_SHIFT); - assert(i>=1); + assert(i >= 1); --i; x = v->long_value.ob_digit[i]; assert(x < PyHASH_MODULUS); -#if ( PyHASH_BITS > (2 * PyLong_SHIFT) ) +#if PyHASH_BITS >= 2 * PyLong_SHIFT // unroll second digit - assert(i>=1); + assert(i >= 1); --i; x <<= PyLong_SHIFT; x += v->long_value.ob_digit[i]; From 9b6e628055705fd6d9b595a26b668286da66ad96 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 4 Sep 2025 14:01:18 +0200 Subject: [PATCH 16/16] Update Objects/longobject.c --- Objects/longobject.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/longobject.c b/Objects/longobject.c index a5f12bc5e986ce..7157e496ee63c9 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3692,6 +3692,7 @@ long_hash(PyObject *obj) 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