From 69aab954494f2f30f13102ef66fccf6b1aeed8b1 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 14 Aug 2019 20:41:58 -0700 Subject: [PATCH 1/7] Use the new pow(base, -1, mod) function when possible --- Lib/fractions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index e774d58e403539..ad06dbfb887f09 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -567,7 +567,10 @@ def __hash__(self): # dinv is the inverse of self._denominator modulo the prime # _PyHASH_MODULUS, or 0 if self._denominator is divisible by # _PyHASH_MODULUS. - dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS) + try: + dinv = pow(self._denominator, -1, _PyHASH_MODULUS) + except ValueError: + dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS) if not dinv: hash_ = _PyHASH_INF else: From 7d9079c9f2d675cc58acf56258fe9f31a35e81fc Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 14 Aug 2019 20:44:33 -0700 Subject: [PATCH 2/7] Time bounded multiplication --- Lib/fractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index ad06dbfb887f09..741e8c6e46eea1 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -574,7 +574,7 @@ def __hash__(self): if not dinv: hash_ = _PyHASH_INF else: - hash_ = abs(self._numerator) * dinv % _PyHASH_MODULUS + hash_ = hash(abs(self._numerator)) * dinv % _PyHASH_MODULUS result = hash_ if self >= 0 else -hash_ return -2 if result == -1 else result From 54b8eb902f9ac1b9053d7b595de4c6d00e0d5e7e Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 14 Aug 2019 20:47:52 -0700 Subject: [PATCH 3/7] bpo-37863: Optimize Fraction.__hash__() --- .../NEWS.d/next/Library/2019-08-14-20-46-39.bpo-37863.CkXqgX.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2019-08-14-20-46-39.bpo-37863.CkXqgX.rst diff --git a/Misc/NEWS.d/next/Library/2019-08-14-20-46-39.bpo-37863.CkXqgX.rst b/Misc/NEWS.d/next/Library/2019-08-14-20-46-39.bpo-37863.CkXqgX.rst new file mode 100644 index 00000000000000..90df6e9cb65266 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-14-20-46-39.bpo-37863.CkXqgX.rst @@ -0,0 +1 @@ +Optimizations for Fraction.__hash__ suggested by Tim Peters. From 70903b4f5ab85ce88d5a96ca062083c36acc117d Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 14 Aug 2019 21:12:32 -0700 Subject: [PATCH 4/7] Simplify expression that evaluates to a constant. --- Lib/fractions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 741e8c6e46eea1..89eac9049df854 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -570,7 +570,8 @@ def __hash__(self): try: dinv = pow(self._denominator, -1, _PyHASH_MODULUS) except ValueError: - dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS) + # ValueError means there is no modular inverse + dinv = 0 if not dinv: hash_ = _PyHASH_INF else: From e4de515b6f63e528b2fea1771b67b117859c28b7 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 14 Aug 2019 22:58:08 -0700 Subject: [PATCH 5/7] Faster sign check (suggested by Serhiy Storchaka) --- Lib/fractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 89eac9049df854..9738c70c7d38c4 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -576,7 +576,7 @@ def __hash__(self): hash_ = _PyHASH_INF else: hash_ = hash(abs(self._numerator)) * dinv % _PyHASH_MODULUS - result = hash_ if self >= 0 else -hash_ + result = hash_ if self._numerator >= 0 else -hash_ return -2 if result == -1 else result def __eq__(a, b): From 7cb3c5df22fbbbe47a3ec542e4c22c4091805e7a Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 14 Aug 2019 23:23:01 -0700 Subject: [PATCH 6/7] Further clean-up --- Lib/fractions.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 9738c70c7d38c4..451daa0acb672c 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -556,23 +556,16 @@ def __round__(self, ndigits=None): def __hash__(self): """hash(self)""" - # XXX since this method is expensive, consider caching the result - # In order to make sure that the hash of a Fraction agrees # with the hash of a numerically equal integer, float or # Decimal instance, we follow the rules for numeric hashes # outlined in the documentation. (See library docs, 'Built-in # Types'). - # dinv is the inverse of self._denominator modulo the prime - # _PyHASH_MODULUS, or 0 if self._denominator is divisible by - # _PyHASH_MODULUS. try: - dinv = pow(self._denominator, -1, _PyHASH_MODULUS) + dinv = pow(self._denominator, -1, _PyHASH_MODULUS) except ValueError: # ValueError means there is no modular inverse - dinv = 0 - if not dinv: hash_ = _PyHASH_INF else: hash_ = hash(abs(self._numerator)) * dinv % _PyHASH_MODULUS From 6c322133dbcb3be380a5dc5d35f823b65fe48ca3 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 14 Aug 2019 23:51:12 -0700 Subject: [PATCH 7/7] Reflow the block comment --- Lib/fractions.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 451daa0acb672c..c922c38e244116 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -556,11 +556,10 @@ def __round__(self, ndigits=None): def __hash__(self): """hash(self)""" - # In order to make sure that the hash of a Fraction agrees - # with the hash of a numerically equal integer, float or - # Decimal instance, we follow the rules for numeric hashes - # outlined in the documentation. (See library docs, 'Built-in - # Types'). + # To make sure that the hash of a Fraction agrees with the hash + # of a numerically equal integer, float or Decimal instance, we + # follow the rules for numeric hashes outlined in the + # documentation. (See library docs, 'Built-in Types'). try: dinv = pow(self._denominator, -1, _PyHASH_MODULUS)