This is a simple repeatable question regarding the usage of Python3's comprehensions:
Could the Python3 syntax be used in another way to speed up the process, further so the gap between Python3 and numpy would be reduced? (see chart below).
The results & code:
Above: Runtime test of Python3's list comprehension, dict comprehension, python for loop and numpy
Update 1 Above: Runtime test without power-functions including Janne Karila suggestion to break down the power function to x * x * x
Update2 Above: Runtime test without powerfunctions and with Justin Peels recommendation to use return list(a.values())
#/usr/bin/python3 -
import sys
from datetime import datetime
import numpy
import matplotlib.pyplot as pyplot
def doingnothing(n):
for i in range(n):
pass
return []
def numpysum(n): #NPY
a = numpy.arange(n) ** 2.
b = numpy.arange(n) ** 3.
c = a + b
return c
def listexpression(n): #LE
#return [x**2+x**3 for x in range(n)]
return [x*x+x*x*x for x in range(n)]
def dictcomprehension(n): #DC
# a = {x:x**2+x**3 for x in range(n)}
a = {x:x*x+x*x*x for x in range(n)}
# return [a[key] for key in a]
return list(a.values())
def pythonsum(n): #PS
a = list(range(n))
b = list(range(n))
c = []
for i in range(len(a)):
# a[i] = i ** 2.
a[i] = i * i
# b[i] = i ** 3.
b[i] = i * i * i
c.append(a[i] + b[i])
return c
def runtimetest(size,verbose=True):
v = verbose
d = {'DoingNothing':None,
'NPY':None,
'LE':None,
'DC':None,
'PS':None}
start = datetime.now()
c = doingnothing(size)
d['DoingNothing'] = datetime.now() - start
start = datetime.now()
c = numpysum(size)
d['NPY'] = datetime.now() - start
start = datetime.now()
c = listexpression(size)
d['LE'] = datetime.now() - start
start = datetime.now()
c = dictcomprehension(size)
d['DC'] = datetime.now() - start
start = datetime.now()
c = pythonsum(size)
d['PS'] = datetime.now() - start
if v:
for key in sorted(d):
print(key, "took", round(d[key].seconds + d[key].microseconds/10**6,6), " sec.")
else:
return d
def view(results):
"""
result['header']=['DoingNothing','NPY','LE','DC','PS']
result[3000]=[0.0, 0.0, 0.002, 0.003, 0.003001]
"""
if 'header' in results.keys():
results.pop('header')
steps,DN,NPY,LE,DC,PS=[],[],[],[],[],[] #I love multiple assignment!
for step in sorted(results):
steps.append(step)
DN.append(results[step][0])
NPY.append(results[step][4])
LE.append(results[step][5])
DC.append(results[step][6])
PS.append(results[step][4])
pyplot.plot(steps,DN)
pyplot.plot(steps,NPY)
pyplot.plot(steps,LE)
pyplot.plot(steps,DC)
pyplot.plot(steps,PS)
pyplot.legend(['Empty Loop', 'Numpy.Arange', 'List Comprehension', 'Dict Comprehension', 'Python for Loop'], loc='upper left')
scale = 'linear'
pyplot.xscale(scale)
pyplot.yscale(scale)
pyplot.title('runtime test')
pyplot.xlabel('length of list')
pyplot.ylabel('runtime in seconds')
pyplot.show()
def longruntimetest(length):
if length<10**4:
length=10**4
result = {}
result['header']=['DoingNothing','NPY','LE','DC','PS']
for step in range(10**3, length+1,10**3):
t=runtimetest(step,verbose=False)
result[step]=[t['DoingNothing'],t['NPY'],t['LE'],t['DC'],t['PS']]
for i in range(len(result[step])):
result[step][i]=round(result[step][i].seconds
+result[step][i].microseconds/10**6,6)
view(result)
def main():
if sys.version_info <= (3, 2):
sys.stdout.write("Sorry, requires Python 3.2.x, not Python 2.x\n")
sys.exit(1)
testsize=10**5
runtimetest(testsize)
longruntimetest(testsize)
if __name__=='__main__':
main()
numpysum
function by callingarange
only once (for me a factor of about .75).a = np.arange(n); c = a**2 + a**3
Still, @Janne's solution helps even more. \$\endgroup\$a = np.arange(n); b = a * a; c = b + b * a
\$\endgroup\$return list(a.values())
is faster on my machine than the list comprehension you are using. \$\endgroup\$for i in range(n): a = i * i; b = a * i; c.append(a + b
or something similar to that? \$\endgroup\$