fastnumbers Functions Compared to Equivalent SolutionsIn order for you to see the benefit of fastnumbers, some timings are collected below for comparison to equivalent python implementations. The numbers may change depending on the machine you are on or the Python version you are using.
Feel free to download this Jupyter Notebook and run the tests yourself to see how fastnumbers performs on your machine (it takes about 1-2 minutes total).
This notebook contains timing results for Python 3.7.
fastnumbers functions do not suffer from as much overhead because they are C-extensions.int function, so the fastnumbers speedup is much larger on Python 2.7 than Python 3.xTimer class belowThe timing runner class is implemented below, and this is used in all the tests to perform the actual timing tests in the sections below. In general you can skip this implementation, but of note is the THINGS_TO_TIME tuple, which contains the values that are passed to the functions to type the various input types.
In [1]:
from __future__ import print_function, division
import re
import math
import timeit
from IPython.display import Markdown, display, clear_output
class Timer(object):
"""Class to time functions and make pretty tables of the output."""
# This is a list of all the things we will time with an associated label.
THINGS_TO_TIME = (
('not_a_number', 'Non-number String'),
('-4', 'Small Int String'),
('-41053', 'Int String'),
('35892482945872302493947939485729', 'Large Int String'),
('-4.1', 'Small Float String'),
('-41053.543034e34', 'Float String'),
('-41053.543028758302e256', 'Large Float String'),
(-41053, 'Int'),
(-41053.543028758302e100, 'Float'),
)
# Formatting strings.
FUNCTION_CALL_FMT = '{}({!r})'
def __init__(self, title):
display(Markdown('### ' + title))
self.functions = []
def add_function(self, func, label, setup='pass'):
"""Add a function to be timed and compared."""
self.functions.append((func, setup, label))
def time_functions(self, repeat=5):
"""Time all the given functions against all input then display results."""
# Collect the function labels to make the header of this table.
# Show that the units are seconds for each.
function_labels = [label + ' (ms)' for _, _, label in self.functions]
# Construct the table strings, formatted in Markdown.
# Store each line as a string element in a list.
# This portion here is the table header only for now.
table = Table()
table.add_header('Input type', *function_labels)
# For each value, time each function and collect the results.
for value, value_label in self.THINGS_TO_TIME:
row = []
for func, setup, _ in self.functions:
call = self.FUNCTION_CALL_FMT.format(func, value)
try:
row.append(self._timeit(call, setup, repeat))
except (ValueError, TypeError):
# We might send in some invalid input accidentally.
# Ignore those inputs.
break
# Only add this row if the for loop quit without break.
else:
# Convert to milliseconds
row = [(mean * 1000, stddev * 1000) for mean, stddev in row]
# Make the lowest value bold.
min_indx = min(enumerate(row), key=lambda x: x[1])[0]
row = ['{:.3f} ± {:.3f}'.format(*x) for x in row]
row[min_indx] = self.bold(row[min_indx])
table.add_row(value_label, *row)
# Show the results in a table.
display(Markdown(str(table)))
@staticmethod
def mean(x):
return math.fsum(x) / len(x)
@staticmethod
def stddev(x):
mean = Timer.mean(x)
sum_of_squares = math.fsum((v - mean)**2 for v in x)
return math.sqrt(sum_of_squares / (len(x) - 1))
@staticmethod
def bold(x):
return "**{}**".format(x)
def _timeit(self, call, setup, repeat=5):
"""Perform the actual timing and return a formatted string of the runtime"""
result = timeit.repeat(call, setup, number=100000, repeat=repeat)
return self.mean(result), self.stddev(result)
class Table(list):
"""List of strings that can be made into a Markdown table."""
def add_row(self, *elements):
self.append('|'.join(elements))
def add_header(self, *elements):
self.add_row(*elements)
seperators = ['---'] * len(elements)
seperators = [sep + (':' if i != 0 else '') for i, sep in enumerate(seperators)]
self.add_row(*seperators)
def __str__(self):
return '\n'.join(self)
import sys
print(sys.version_info)
In [2]:
timer = Timer('Timing comparison of `int` functions')
timer.add_function('int', 'builtin')
timer.add_function('int', 'fastnumbers', 'from fastnumbers import int')
timer.time_functions(repeat=100)
In [3]:
timer = Timer('Timing comparison of `float` functions')
timer.add_function('float', 'builtin')
timer.add_function('float', 'fastnumbers', 'from fastnumbers import float')
timer.time_functions(repeat=100)
In [4]:
def int_re(x, int_match=re.compile(r'[-+]?\d+$').match):
"""Function to simulate fast_int but with regular expressions."""
try:
if int_match(x):
return int(x)
else:
return x
except TypeError:
return int(x)
def int_try(x):
"""Function to simulate fast_int but with try/except."""
try:
return int(x)
except ValueError:
return x
timer = Timer('Timing comparison of `int` functions with error handling')
timer.add_function('int_try', 'try/except', 'from __main__ import int_try')
timer.add_function('int_re', 'regex', 'from __main__ import int_re')
timer.add_function('fast_int', 'fastnumbers', 'from fastnumbers import fast_int')
timer.time_functions()
In [5]:
def float_re(x, float_match=re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match):
"""Function to simulate fast_float but with regular expressions."""
try:
if float_match(x):
return float(x)
else:
return x
except TypeError:
return float(x)
def float_try(x):
"""Function to simulate fast_float but with try/except."""
try:
return float(x)
except ValueError:
return x
timer = Timer('Timing comparison of `float` functions with error handling')
timer.add_function('float_try', 'try/except', 'from __main__ import float_try')
timer.add_function('float_re', 'regex', 'from __main__ import float_re')
timer.add_function('fast_float', 'fastnumbers', 'from fastnumbers import fast_float')
timer.time_functions()
In [6]:
def real_re(x,
int_match=re.compile(r'[-+]?\d+$').match,
real_match=re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match):
"""Function to simulate fast_real but with regular expressions."""
try:
if int_match(x):
return int(x)
elif real_match(x):
return float(x)
else:
return x
except TypeError:
if type(x) in (float, int):
return x
else:
raise TypeError
def real_try(x):
"""Function to simulate fast_real but with try/except."""
try:
a = float(x)
except ValueError:
return x
else:
b = int(a)
return b if a == b else b
timer = Timer('Timing comparison of `float` (but coerce to `int` if possible) functions with error handling')
timer.add_function('real_try', 'try/except', 'from __main__ import real_try')
timer.add_function('real_re', 'regex', 'from __main__ import real_re')
timer.add_function('fast_real', 'fastnumbers', 'from fastnumbers import fast_real')
timer.time_functions()
In [7]:
def forceint_re(x,
int_match=re.compile(r'[-+]\d+$').match,
float_match=re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match):
"""Function to simulate fast_forceint but with regular expressions."""
try:
if int_match(x):
return int(x)
elif float_match(x):
return int(float(x))
else:
return x
except TypeError:
return int(x)
def forceint_try(x):
"""Function to simulate fast_forceint but with try/except."""
try:
return int(x)
except ValueError:
try:
return int(float(x))
except ValueError:
return x
timer = Timer('Timing comparison of forced `int` functions with error handling')
timer.add_function('forceint_try', 'try/except', 'from __main__ import forceint_try')
timer.add_function('forceint_re', 'regex', 'from __main__ import forceint_re')
timer.add_function('fast_forceint', 'fastnumbers', 'from fastnumbers import fast_forceint')
timer.time_functions()
In [8]:
def isint_re(x, int_match=re.compile(r'[-+]?\d+$').match):
"""Function to simulate isint but with regular expressions."""
t = type(x)
return t == int if t in (float, int) else bool(int_match(x))
def isint_try(x):
"""Function to simulate isint but with try/except."""
try:
int(x)
except ValueError:
return False
else:
return type(x) != float
timer = Timer('Timing comparison to check if value can be converted to `int`')
timer.add_function('isint_try', 'try/except', 'from __main__ import isint_try')
timer.add_function('isint_re', 'regex', 'from __main__ import isint_re')
timer.add_function('isint', 'fastnumbers', 'from fastnumbers import isint')
timer.time_functions()
In [9]:
def isfloat_re(x, float_match=re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match):
"""Function to simulate isfloat but with regular expressions."""
t = type(x)
return t == float if t in (float, int) else bool(float_match(x))
def isfloat_try(x):
"""Function to simulate isfloat but with try/except."""
try:
float(x)
except ValueError:
return False
else:
return type(x) != int
timer = Timer('Timing comparison to check if value can be converted to `float`')
timer.add_function('isfloat_try', 'try/except', 'from __main__ import isfloat_try')
timer.add_function('isfloat_re', 'regex', 'from __main__ import isfloat_re')
timer.add_function('isfloat', 'fastnumbers', 'from fastnumbers import isfloat')
timer.time_functions()
In [10]:
def isreal_re(x, real_match=re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match):
"""Function to simulate isreal but with regular expressions."""
return type(x) in (float, int) or bool(real_match(x))
def isreal_try(x):
"""Function to simulate isreal but with try/except."""
try:
float(x)
except ValueError:
return False
else:
return True
timer = Timer('Timing comparison to check if value can be converted to `float` or `int`')
timer.add_function('isreal_try', 'try/except', 'from __main__ import isreal_try')
timer.add_function('isreal_re', 'regex', 'from __main__ import isreal_re')
timer.add_function('isreal', 'fastnumbers', 'from fastnumbers import isreal')
timer.time_functions()
In [11]:
def isintlike_re(x,
int_match=re.compile(r'[-+]?\d+$').match,
float_match=re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match):
"""Function to simulate isintlike but with regular expressions."""
try:
if int_match(x):
return True
elif float_match(x):
return float(x).is_integer()
else:
return False
except TypeError:
return int(x) == x
def isintlike_try(x):
"""Function to simulate isintlike but with try/except."""
try:
a = int(x)
except ValueError:
try:
a = float(x)
except ValueError:
return False
else:
return a.is_integer()
else:
return a == float(x)
timer = Timer('Timing comparison to check if value can be coerced losslessly to `int`')
timer.add_function('isintlike_try', 'try/except', 'from __main__ import isintlike_try')
timer.add_function('isintlike_re', 'regex', 'from __main__ import isintlike_re')
timer.add_function('isintlike', 'fastnumbers', 'from fastnumbers import isintlike')
timer.time_functions()
In [ ]: