I work a lot with computation times for my master's dissertation. During exploratory development I'm usually using iPython where the %timeit
and %time
magic commands usually serve my purpose. Their use is quite simple. The %time
returns the elapsed time for the expression that follows. The %timeit
magic command allows for control over how many times should the expression be executed in a loop, how many loops, precision of the result, among others. In short, it's closer in functionality to Python's timeit module.
In [1]:
%time a = [i**2 for i in range(10000)]
In [2]:
%timeit a = [i**2 for i in range(10000)]
There are two things I still would like to do though. The first is to store the times in variables. The second is that I often would like to time a specific method from a class without having to programmatically change the class. To address this, I created a simple stopwatch Python class based on the timeit module. I like Matlab's names for their stopwatch commands, so I based myself on them (I think tac is a better sound toc but that's another matter).
In [23]:
from timeit import default_timer as timer
class Timer():
def __init__(self, ID = None):
self.start = None
self.end = None
self.elapsed = 0
def tic(self):
self.start = timer()
def tac(self):
self.end = timer()
self.elapsed += self.end - self.start
return self.elapsed
def reset(self):
self.elapsed = 0
t = Timer()
t.tic()
a = [i**2 for i in range(10000)]
t.tac()
Out[23]:
The second issue was more interesting and is actually the main reason I'm writing this post. Python's functions are just objects. I can passed them around as arguments if I want. This means it's quite easy to create a wrapper function around an input funtion. Here's an example of a wrapper function that will time the input function.
In [32]:
def wrapper_add(fn, *args, **kwargs):
t = Timer()
t.tic()
returnvals = fn(*args, **kwargs)
t.tac()
return returnvals, t.elapsed
add = lambda x, y: x + y
wrapper_add(add, 1, 1)
Out[32]:
My idea was then to generate a wrapper function that would time the input function. Since Python's functions are objects I can create them inside a funciton and then return them. So here is what I came up with:
In [ ]:
class Timer():
# ... other methods ...
def wrap_function(self, fn):
def timed_fn(*args,**kwargs):
self.tic()
returnvals = fn(*args,**kwargs)
self.tac()
return returnvals
return timed_fn
This method is taking in a function then creates a wrapper function like the one in the last example and returns it. It's that simple.
I can create a normal timer like in the above examples and then I can wrap a function with that specific timer. Whenever that function is executed it increments the timer with the elapsed time.
In [35]:
add = lambda x, y: x + y
t = Timer()
print t.elapsed
add_t = t.wrap_function(add)
add_t(2,2)
print t.elapsed
add_t = t.wrap_function(add)
add_t(2,2)
print t.elapsed
So I made a wrapper function generator but I still haven't changed an object's methods so they're timed as well. I'll show that now.
In [127]:
import numpy as np
class Dummy:
def __init__(self, x, y):
self.x = np.random.random(x)
self.y = np.random.random(y)
def _add(self):
self.total = self.x + self.y
return self.total
t1 = Timer()
t2 = Timer()
d = Dummy(10000000, 10000000)
t1.tic()
d._add()
t1.tac()
print d.total.sum(), t1.elapsed * 1000
d._add = t2.wrap_function(d._add)
d._add()
print d.total.sum(), t2.elapsed * 1000
There is a caveat to this, though. The object's attributes have to be writtable. If they're read-only then it's no play, as can be seen when I tried this on a NumPy array method.
In [130]:
import numpy as np
t = Timer()
arr = np.random.random(100000)
arr.sort = t.wrap_function(arr.argsort)