Skip to content

Cache the hash value of fractions #96465

Closed
@rhettinger

Description

@rhettinger

In both 3.10 and 3.11, hashing a small fraction is 20x slower than for a decimal. For fractions with large denominators, it is slower still.

We could sacrifice the space for one slot to cache the hash value:

__slots__ = ('_numerator', '_denominator', '_hash') def __hash__(self): try: return self._hash except AttributeError: self.hash = ... # current code goes here return self._hash 

Another approach would be to have a single, size limited cache for the computation:

def __hash__(self): @lru_cache(maxsize=1 << 16) def current_code(numerator, denominator): try: dinv = pow(denominator, -1, _PyHASH_MODULUS) except ValueError: # ValueError means there is no modular inverse. hash_ = _PyHASH_INF else: hash_ = hash(hash(abs(numerator)) * dinv) result = hash_ if numerator >= 0 else -hash_ return -2 if result == -1 else result return current_code(self._numerator, self._denominator) 

Example of code that would benefit:

@lru_cache(maxsize=100_000) def pabs(n: Fraction | int) -> Fraction: "P-adic absolute value" ... for x, y in product(pool, repeat=2): # pool is a set of fractions used as test values assert (pabs(x)==0) == (x==0) # |x|=0 if and only if x=0 assert pabs(x * y) == pabs(x) * pabs(y) # |xy| = |x||y| assert pabs(x + y) <= pabs(x) + pabs(y) # |x + y| ≤ |x| + |y| (Triangle Inequality) assert pabs(x + y) <= max(pabs(x), pabs(y)) # Strong triangle inequality assert pabs(x) == pabs(y) or pabs(x + y) == max(pabs(x), pabs(y)) # Lemma 2.1: If |x| ≠ |y|, |x+y| = max{|x|, |y|} 

Metadata

Metadata

Assignees

No one assigned

    Labels

    performancePerformance or resource usagetype-featureA feature request or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions