GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Cache the hash value of fractions · Issue #96465 · python/cpython

Cache the hash value of fractions #96465

New issue
Copy link
New issue
Copy link
Closed
Closed
Cache the hash value of fractions#96465
Copy link
Labels
performancePerformance or resource usagePerformance or resource usagetype-featureA feature request or enhancementA feature request or enhancement

Description

@rhettinger
rhettinger
opened on Aug 31, 2022
Issue body actions

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|}

Activity

  1. rhettinger
    added
    type-featureA feature request or enhancementA feature request or enhancement
    on Aug 31, 2022
  2. AlexWaygood
    added
    performancePerformance or resource usagePerformance or resource usage
    on Aug 31, 2022
  3. serhiy-storchaka commented on Aug 31, 2022

    @serhiy-storchaka
    serhiy-storchaka
    on Aug 31, 2022
    Member
    More actions

    See also #58683. The similar patch for Decimal was rejected.

  4. gvanrossum
    added
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    on Sep 1, 2022
  5. gvanrossum commented on Sep 1, 2022

    @gvanrossum
    gvanrossum
    on Sep 1, 2022
    Member
    More actions

    Classic time vs. space trade-off. How common is hashing Fractions? (The example seems a bit special.) If we decide to do this, I'd prefer the version using an LRU cache, so that someone creating a large number of Fractions they aren't hashing doesn't have to pay the price. Did you think about the cache size much or is 65k just your go-to cache size value these days?

    FWIW the Decimal patch for Python was rejected. The C version seems all right, so the problem would only affect the tiny minority of people who can't use the C Decimal version. So that's not comparable here.

  6. rhettinger
    added a commit that references this issue on Sep 1, 2022

    pythonGH-96465: Cache hashes for Fraction instances

    12a17c6
  7. matthiasgoergens commented on Sep 6, 2022

    @matthiasgoergens
    matthiasgoergens
    on Sep 6, 2022
    Contributor
    More actions

    @rhettinger Do you have have benchmark data?

  8. rhettinger commented on Sep 7, 2022

    @rhettinger
    rhettinger
    on Sep 7, 2022
    ContributorAuthor
    More actions

    Baseline

    % ~/cpython/python.exe -m timeit -s 'from fractions import Fraction as F' -s 'f = F(375, 34)' 'hash(f)'
    500000 loops, best of 5: 479 nsec per loop

    With caching

    % git checkout fraction_hash_cache
    Switched to branch 'fraction_hash_cache'
    % ~/cpython/python.exe -m timeit -s 'from fractions import Fraction as F' -s 'f = F(375, 34)' 'hash(f)'
    2000000 loops, best of 5: 119 nsec per loop

    Reacted by Matthias Görgens
  9. rhettinger
    closed this as completedon Sep 7, 2022
  10. rhettinger
    added a commit that references this issue on Sep 7, 2022

    GH-96465: Cache hashes for Fraction instances (GH-96483)

    3eaf70d
  11. zware
    added a commit that references this issue on Sep 8, 2022

    pythongh-96465: Clear fractions hash lru_cache under refleak testing

    1c68a75
  12. miss-islington
    added a commit that references this issue on Sep 8, 2022

    gh-96465: Clear fractions hash lru_cache under refleak testing (GH-96689

    9c8f379
  13. zware
    removed
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    on Sep 8, 2022
  14. vstinner
    added 2 commits that reference this issue on Sep 2, 2023

    pythongh-96465: Clear fractions hash lru_cache under refleak testing (p…

    693a035

    [3.11] gh-108822: Backport libregrtest changes from the main branch (#…

    79f7a4c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    performancePerformance or resource usagePerformance or resource usagetype-featureA feature request or enhancementA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.