Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Lib/test/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -1693,5 +1693,22 @@ class MyInt(int):
# GH-117195 -- This shouldn't crash
object.__sizeof__(1)

def test_hash(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that part could be in a separate pr, merged first.

The docs describe algorithm in details:
https://docs.python.org/3/library/stdtypes.html#hashing-of-numeric-types

Maybe we could test it against pure-Python implementation, using also hypothesis?

# gh-136599
self.assertEqual(hash(-1), -2)
self.assertEqual(hash(0), 0)
self.assertEqual(hash(10), 10)

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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertEqual(hash(-sys.hash_info.modulus + 1), - (sys.hash_info.modulus - 1))
self.assertEqual(hash(-sys.hash_info.modulus + 1), -sys.hash_info.modulus + 1)



if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve performance of :class:`int` hash calculations.
15 changes: 14 additions & 1 deletion Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3676,7 +3676,20 @@ long_hash(PyObject *obj)
}
i = _PyLong_DigitCount(v);
sign = _PyLong_NonCompactSign(v);
x = 0;

// unroll first two digits
#if ( PyHASH_BITS > PyLong_SHIFT )
Copy link
Contributor

@skirpichev skirpichev Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyHASH_BITS can be either 31 or 61, while PyLong_SHIFT - either 15 or 30. So, this condition is always true.

BTW, it seems the 15-bit digit is untested by regular CI. Is there at least some buildbot with such settings? I'll open an issue.

Suggested change
#if ( PyHASH_BITS > PyLong_SHIFT )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case is indeed untested, but I added it because of a comment by Serhiy #136600 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case is indeed untested

FYI: #138336

I added it because of a comment by Serhiy

Then fine. Though, an assert might be option.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace the test with a build assertion: Py_BUILD_ASSERT(PyHASH_BITS > PyLong_SHIFT);

assert(i>=2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just use assert(i>=1) here and then again in the second #if below?

Otherwise LGTM.

--i;
x = v->long_value.ob_digit[i];
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);
#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
Expand Down
Loading