For this benchmark, I only want to focus on the simplest approaches that don't require any additional libraries or modules that are not part of Python's in-built functions or the standard library.
Why? Because in most simple Python scripts I often see people using the math module for those calculations and I was curious to see if we are not better off using ,e.g., the basic arithmetic operator $x^{0.5}$ and the in-built pow
function for exponential functions.
math.sqrt(x)
x**0.5
Note that we'll import the math.sqrt() and math.pow() functions into the global namespace to reduce the overhead:
import math
from math import sqrt as math_sqrt
x = 81
%timeit math_sqrt(x)
%timeit math.sqrt(x)
10000000 loops, best of 3: 140 ns per loop 10000000 loops, best of 3: 177 ns per loop
x = 988
assert(math_sqrt(x) == x**0.5)
print('both functions produce similar results')
both functions produce similar results
math.pow(x, n)
pow(x, n)
x**n
from math import pow as math_pow
x = 3.3
n = 4
assert(math_pow(x, n) == pow(x, n) == x**n)
print('All functions produce similar results')
All functions produce similar results
import timeit
test_sqrt = [12345.54321, 54756, 23423422, 999999999999999]
timings_sqrt = {'math_module':[], 'arithmetic':[]}
for x in test_sqrt:
timings_sqrt['math_module'].append(min(timeit.Timer('math_sqrt(x)',
'from __main__ import x, math_sqrt')
.repeat(repeat=100, number=1000)))
timings_sqrt['arithmetic'].append(min(timeit.Timer('x**0.5',
'from __main__ import x')
.repeat(repeat=100, number=1000)))
import timeit
test_pow = list(range(1,13))
funcs_pow = ['math_module', 'inbuilt', 'arithmetic']
timings_pow = {f:[] for f in funcs_pow}
for n in test_pow:
timings_pow['math_module'].append(min(timeit.Timer('math_pow(n, n)',
'from __main__ import n, math_pow')
.repeat(repeat=100, number=1000)))
timings_pow['inbuilt'].append(min(timeit.Timer('pow(n, n)',
'from __main__ import n')
.repeat(repeat=100, number=1000)))
timings_pow['arithmetic'].append(min(timeit.Timer('n**n',
'from __main__ import n')
.repeat(repeat=100, number=1000)))
import platform
import multiprocessing
def print_sysinfo():
print('\nsystem :', platform.system())
print('release :', platform.release())
print('machine :', platform.machine())
print('processor:', platform.processor())
print('interpreter:', platform.architecture()[0])
print('CPU count :', multiprocessing.cpu_count())
print('\nPython version', platform.python_version())
print('compiler', platform.python_compiler())
print('\n\n')
%matplotlib inline
import matplotlib.pyplot as plt
def plot_figures():
fig = plt.figure(figsize=(8,4))
plt.plot(range(len(test_sqrt)), timings_sqrt['math_module'],
alpha=0.5, label='math.sqrt(x)', marker='o', lw=2)
plt.plot(range(len(test_sqrt)), timings_sqrt['arithmetic'],
alpha=0.5, label='x**0.5', marker='o', lw=2)
plt.legend(loc='upper left')
plt.xticks(range(len(test_sqrt)), ['$\sqrt{%s}$'%(x) for x in test_sqrt])
plt.grid()
plt.ylabel('time in milliseconds')
plt.show()
fig = plt.figure(figsize=(8,4))
plt.plot(range(len(test_pow)), timings_pow['math_module'],
alpha=0.5, label='math.pow(x, n)', marker='o', lw=2)
plt.plot(range(len(test_pow)), timings_pow['inbuilt'],
alpha=0.5, label='pow(x, n)', marker='o', lw=2)
plt.plot(range(len(test_pow)), timings_pow['arithmetic'],
alpha=0.5, label='x**n', marker='o', lw=2)
plt.legend(loc='upper left')
plt.xticks(range(len(test_pow)), ['$%s^{%s}$'%(x, x) for x in test_pow])
plt.grid()
plt.ylabel('time in milliseconds')
print_sysinfo()
plot_figures()
system : Darwin release : 13.1.0 machine : x86_64 processor: i386 interpreter: 64bit CPU count : 4 Python version 3.4.0 compiler GCC 4.2.1 (Apple Inc. build 5577)
Despite the additional name-lookup for the math
-module functions, those approaches are still faster than the in-built **
operator and pow
function.
Why is the math
module more efficient?
The math
module uses the C implementations of the square root and power functions which explains the better performance.
And pow()
is really the same as the **
operator but comes with an additional function call overhead.
However, the math module comes with the disadvantage that it can't handle complex numbers. In those cases we'd need to use the cmath
module.
(81+2j)**0.5
(9.000685740426075+0.11110264582492375j)
import cmath
cmath.sqrt(81+2j)
(9.000685740426075+0.11110264582492377j)
import math
math.sqrt(81+2j)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-4-0ed1cc6d12dd> in <module>() 1 import math ----> 2 math.sqrt(81+2j) TypeError: can't convert complex to float