New submission from Wolfgang Maier:

I've spent a bit of time lately trying to optimize the instantiation of 
Fractions. This is related to Issue22464, but instead of focusing on 
constructing Fractions from ints, my attempts revolve around improving 
instantiation from strings, floats and decimals.
I'm attaching a patch with all my changes for discussion, but here's an 
overview of what's in it:

CHANGES TO INSTANTIATION FROM STRINGS:

- isinstance checking for str is performed before the more expensive check for 
numbers.Rational (which has to go through abc)

- instantiation from strings doesn't use a regex pattern anymore for parsing; 
this is faster in many cases (and never slower) than the current version

- while reimplementing string parsing I added PEP 515 support (this is the only 
behavior change in the patch)

combined this gives me the following performance changes for instantiation of 
Fractions from different arguments (profiled with 1,000,000 random inputs each):

'x/y'-type of strings:
----------------------
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000   12.162    0.000   27.778    0.000 fractions.py:84(__new__)
new:
  1000000    9.619    0.000   12.428    0.000 frc.py:68(__new__)


scientific notation (e.g., '1.234E-7'):
---------------------------------------
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000   18.209    0.000   37.341    0.000 fractions.py:84(__new__)
new:
  1000000   15.509    0.000   21.252    0.000 frc.py:68(__new__)


integer strings (e.g. '123456'):
--------------------------------
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000   11.272    0.000   26.201    0.000 fractions.py:84(__new__)
new:
  1000000    9.347    0.000   12.425    0.000 frc.py:68(__new__)


from other Fractions (to test effect on instantiation of numbers.Rational):
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000    4.708    0.000   10.093    0.000 fractions.py:84(__new__)
new:
  1000000    4.835    0.000   10.572    0.000 frc.py:68(__new__)

from int subclass (as another numbers.Rational):
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000    3.446    0.000    8.044    0.000 fractions.py:84(__new__)
new:
  1000000    3.795    0.000    8.836    0.000 frc.py:68(__new__)


SUMMARY of this part
====================

+ very significant speedup for instatiation from strings of different forms 
with (near) negligible effects on instantiation from numbers.Rational.

+ correct parsing of PEP 515-like number strings

- possibly slower error bubbling with invalid input (untested)


CHANGES TO INSTANTIATION FROM FLOAT AND DECIMAL:

- no explicit check for float and decimal in standard constructor; instead, 
simply try to call as_integer_ratio as last resort (even after checking for 
numbers.Rational)

- speed up alternate from_float and from_decimal constructor class methods by 
rearranging type checks and making use of _normalize flag

- import decimal only on demand (when using from_decimal)


Resulting performance changes:

standard constructor with float:
--------------------------------
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000    4.362    0.000   12.452    0.000 fractions.py:84(__new__)
new:
  1000000    6.693    0.000   16.020    0.000 frc.py:68(__new__)

from_float:
-----------
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000    3.146    0.000   17.769    0.000 fractions.py:193(from_float)
new:
  1000000    2.544    0.000    7.591    0.000 frc.py:205(from_float)

standard constructor with decimal:
--------------------------------
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000    4.672    0.000   20.795    0.000 fractions.py:84(__new__)
new:
  1000000    7.097    0.000   24.526    0.000 frc.py:68(__new__)

from_decimal:
-------------
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
old:
  1000000    5.054    0.000   34.473    0.000 fractions.py:207(from_decimal)
new:
  1000000    2.942    0.000   16.013    0.000 frc.py:220(from_decimal)

SUMMARY of this part:
- standard constructor becomes a bit slower for floats and Decimals
specialized class methods become a lot faster (for Decimal the benchmarks are 
based on _pydecimal - with the C implementation the percent differences would 
be even larger)
- eliminates decimal from regularly imported modules
- allows Fraction instantiation from duck-typing classes that provide 
as_integer_ratio

I hope at least some of this is interesting.

----------
components: Library (Lib)
files: fractions.patch
keywords: patch
messages: 280977
nosy: wolma
priority: normal
severity: normal
status: open
title: Fractions instantiation revisited
type: behavior
versions: Python 3.7
Added file: http://bugs.python.org/file45507/fractions.patch

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue28716>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to