Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize creation of unnormalized Fraction's in private methods (arithmetics, etc) #101773

Closed
skirpichev opened this issue Feb 10, 2023 · 1 comment
Labels
performance Performance or resource usage stdlib Python modules in the Lib dir type-feature A feature request or enhancement

Comments

@skirpichev
Copy link
Member

skirpichev commented Feb 10, 2023

Right now we just use __new__() with a private kwarg _normalize=False. That's slightly less efficient, then a dedicated class method.

POC patch

diff --git a/Lib/fractions.py b/Lib/fractions.py
index 49a3f2841a..27c75785fb 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -315,6 +315,13 @@ def from_decimal(cls, dec):
                 (cls.__name__, dec, type(dec).__name__))
         return cls(*dec.as_integer_ratio())
 
+    @classmethod
+    def _from_pair(cls, num, den):
+        obj = super(Fraction, cls).__new__(cls)
+        obj._numerator = num
+        obj._denominator = den
+        return obj
+
     def is_integer(self):
         """Return True if the Fraction is an integer."""
         return self._denominator == 1
@@ -703,13 +710,13 @@ def _add(a, b):
         nb, db = b._numerator, b._denominator
         g = math.gcd(da, db)
         if g == 1:
-            return Fraction(na * db + da * nb, da * db, _normalize=False)
+            return Fraction._from_pair(na * db + da * nb, da * db)
         s = da // g
         t = na * (db // g) + nb * s
         g2 = math.gcd(t, g)
         if g2 == 1:
-            return Fraction(t, s * db, _normalize=False)
-        return Fraction(t // g2, s * (db // g2), _normalize=False)
+            return Fraction._from_pair(t, s * db)
+        return Fraction._from_pair(t // g2, s * (db // g2))
 
     __add__, __radd__ = _operator_fallbacks(_add, operator.add)
 

We can drop private kwarg of __new__() after adopting this way.

Some benchmarks

With above patch

$ ./python -m timeit -s 'from fractions import Fraction as Q' -s 'a,b=Q(3,7),Q(5, 8)' -u usec 'a+b'
50000 loops, best of 5: 4.4 usec per loop
$ ./python -m timeit -s 'from fractions import Fraction as Q' -s 'a,b=Q(3,7)**100,Q(5, 8)**100' -u usec 'a+b'
20000 loops, best of 5: 12.6 usec per loop

On the mastermain:

$ ./python -m timeit -s 'from fractions import Fraction as Q' -s 'a,b=Q(3,7),Q(5, 8)' -u usec 'a+b'
50000 loops, best of 5: 6.99 usec per loop
$ ./python -m timeit -s 'from fractions import Fraction as Q' -s 'a,b=Q(3,7)**100,Q(5, 8)**100' -u usec 'a+b'
20000 loops, best of 5: 15.9 usec per loop

Linked PRs

@skirpichev skirpichev added the type-feature A feature request or enhancement label Feb 10, 2023
@arhadthedev arhadthedev added the stdlib Python modules in the Lib dir label Feb 10, 2023
skirpichev added a commit to skirpichev/cpython that referenced this issue Feb 10, 2023
skirpichev added a commit to skirpichev/cpython that referenced this issue Feb 10, 2023
@mdboom mdboom added the performance Performance or resource usage label Feb 10, 2023
mdickinson added a commit that referenced this issue Feb 27, 2023
This PR adds a private `Fraction._from_coprime_ints` classmethod for internal creations of `Fraction` objects, replacing the use of `_normalize=False` in the existing constructor. This speeds up creation of `Fraction` objects arising from calculations. The `_normalize` argument to the `Fraction` constructor has been removed.

Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
Co-authored-by: Mark Dickinson <dickinsm@gmail.com>
@mdickinson
Copy link
Member

Done in #101780.

carljm added a commit to carljm/cpython that referenced this issue Feb 28, 2023
* main: (67 commits)
  pythongh-99108: Add missing md5/sha1 defines to Modules/Setup (python#102308)
  pythongh-100227: Move _str_replace_inf to PyInterpreterState (pythongh-102333)
  pythongh-100227: Move the dtoa State to PyInterpreterState (pythongh-102331)
  pythonGH-102305: Expand some macros in generated_cases.c.h (python#102309)
  Migrate to new PSF mailgun account (python#102284)
  pythongh-102192: Replace PyErr_Fetch/Restore etc by more efficient alternatives (in Python/) (python#102193)
  pythonGH-90744: Fix erroneous doc links in the sys module (python#101319)
  pythongh-87092: Make jump target label equal to the offset of the target in the instructions sequence (python#102093)
  pythongh-101101: Unstable C API tier (PEP 689) (pythonGH-101102)
  IDLE: Simplify DynOptionsMenu __init__code (python#101371)
  pythongh-101561: Add typing.override decorator (python#101564)
  pythongh-101825: Clarify that as_integer_ratio() output is always normalized (python#101843)
  pythongh-101773: Optimize creation of Fractions in private methods (python#101780)
  pythongh-102251: Updates to test_imp Toward Fixing Some Refleaks (pythongh-102254)
  pythongh-102296 Document that inspect.Parameter kinds support ordering (pythonGH-102297)
  pythongh-102250: Fix double-decref in COMPARE_AND_BRANCH error case (pythonGH-102287)
  pythongh-101100: Fix sphinx warnings in `types` module (python#102274)
  pythongh-91038: Change default argument value to `False` instead of `0` (python#31621)
  pythongh-101765: unicodeobject: use Py_XDECREF correctly (python#102283)
  [doc] Improve grammar/fix missing word (pythonGH-102060)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance Performance or resource usage stdlib Python modules in the Lib dir type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

4 participants