Skip to content

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

Closed
@skirpichev

Description

@skirpichev

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    performancePerformance or resource usagestdlibPython modules in the Lib dirtype-featureA feature request or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions