Skip to content

Instantly share code, notes, and snippets.

@tyleha
Last active February 16, 2023 00:38
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tyleha/5174230 to your computer and use it in GitHub Desktop.
Save tyleha/5174230 to your computer and use it in GitHub Desktop.
Building a Matlab-style timing function that can be called with the bare-minimum number of keystrokes and thought.

A Simple Python Timing Function

Overview

# What this gist provides:

tic()
'''code to be timed'''
toc()

#Outputs:
#Elapsed time is: 12.3456 seconds

I often work with huge amounts of data, and processing time can be extensive. I spend a decent chunk of time trying to optimize Python to manage that data efficiently, so I find myself logging script run times quite often. I'm not talking about benchmarking cpu time or anything so precise - instead I'm just curious about how long these algorithms take on the scale of seconds to minutes. I have gotten fed up with adding these little snippets all over my dev code in IPython and the like:

import datetime
start = datetime.datetime.now()
 
'''
some elegant, complex code that takes a non-trivial amount of
time to run, and you're trying to optimize it
'''
 
end = datetime.datetime.now()
print start-end
print "This took me %s seconds" %(end-start)

That's a lot of boilerplate code, and just to do something that's crazy simple. I'm sure you could consolidate that down into even fewer lines and keystrokes, but the point remains that you're constantly remembering to import time or datetime, save a start/end time variable, and format it into something that is human readable.

The Super Simple Python Solution

Our ideal solution (AKA the competition)

I started programming in the Matlab world, and engineers at Mathworks try hard to keep your keystrokes at a minimum. I loved the simplicity of Matlab's tic and toc helpers to just to give me a quick gut check of how long a whole process was taking, or even to track overlapping subprocesses within a program. In Matlab, one would just:

tic
% Timed code block
toc

And out came Elapsed time: 48.23412 seconds. Crazy simple, effective, and doesn't require ANY thought. We can do that! But Matlab also allowed you to overlap tics and tocs, so that one could track elapsed time of separate features within one script or program. Well, if I've learned anything from numpy and matplotlib, it's "Anything Matlab can do, Python can do better".

So, with that mantra in mind, I wrote this code one day after work. Implemented in tic-toc-python.py is the basic code to do all of the above. I realize this code is no revelation and no great shakes, but it's super simple and saves you some valuable time. I suggest just downloading this gist and placing the functions somewhere in a .py file you can import from all your scripts. For what its worth, I have a helpers.py file at the top level of my main repo where I put little global functions like this one.

Basic use cases:

# Most basic usage - please tell me how long this took
tic()

''' Code to be timed '''

toc()

And out will come Elapsed time: 4.2323 seconds, with max 4 trailing decimals. Just like Matlab. I feel so at home, and MAN so easy. No clutter, no remembering what you saved the start time as, no formatting thought. If you're timing something especially long-running, you might even set the fmt kwarg:

tic()
''' super long running script '''

toc(fmt=True)

#OUTPUT
#Elapsed time: 01:13:84

And out will come Elapsed time: 01:13:84 formatted all pretty. If you need to save the time output for some particular reason, you can pass the kwarg 'save':

tic()
''' code to test '''

runtime = toc(save=True) 

And you'll get the elapsed time in seconds. Just as in Matlab, we can also overlap multiple timings. But, even BETTER than in Matlab, we can refer to diffent tics and tocs by easy-to-remember tags like so:

tic(tag='load')

''' code that loads a lot of files or downloads stuff from the web, whatever '''

tic(tag='process')

''' code that does something different you want to track '''

# Let's say the downloading is done now
toc(tag='load')

# but maybe the processing doesn't finish till here
toc(tag='process')

#OUTPUT
# load - Elapsed Time: 1234.123456 seconds
# process - Elapsed Time: 1234.123456 seconds

Used as above, the time between the tag='load' and tag='process' will be timed separately and printed out separately for each process. Once again, you can combine other args for save or fmt to your pleasure.

Conclusion

It's also not hard to envision how you could quickly change the default functionality of tic toc to whatever you please. The idea here is to just provide a Super Simple method of timing code; it's nothing Python doesn't already provide. But I, for one, think that Python can learn a thing or two from Matlab. There's nothing wrong with abstracting a few things for simplicity's sake, and to save you the same keystrokes over and over.

# tic toc functions ala Matlab
import time
def tic(tag=None):
'''Start timer function.
tag = used to link a tic to a later toc. Can be any dictionary-able key.
'''
global TIC_TIME
if tag is None:
tag = 'default'
try:
TIC_TIME[tag] = time.time()
except NameError:
TIC_TIME = {tag: time.time()}
def toc(tag=None, save=False, fmt=False):
'''Timer ending function.
tag - used to link a toc to a previous tic. Allows multipler timers, nesting timers.
save - if True, returns float time to out (in seconds)
fmt - if True, formats time in H:M:S, if False just seconds.
'''
global TOC_TIME
template = 'Elapsed time is:'
if tag is None:
tag = 'default'
else:
template = '%s - '%tag + template
try:
TOC_TIME[tag] = time.time()
except NameError:
TOC_TIME = {tag: time.time()}
if TIC_TIME:
d = (TOC_TIME[tag]-TIC_TIME[tag])
if fmt:
print template + ' %s'%time.strftime('%H:%M:%S', time.gmtime(d))
else:
print template + ' %f seconds'%(d)
if save: return d
else:
print "no tic() start time available. Check global var settings"
@Grief
Copy link

Grief commented May 30, 2016

time.time() is not monotonic and must not be used to measure time intervals

@dafuer
Copy link

dafuer commented Nov 8, 2016

Great!

And what about make the code more "pythonic" using "with" statement? I propose to add:

class tictoc():
    def __init__(self, tag=None):
        self.tag = tag
    def __enter__(self):
        tic(tag=self.tag)
    def __exit__(self, type, value, traceback):
        toc(tag=self.tag)

And then, you can use the code in following way:

with tictoc():
    ''' do something '''

Or even with tag:

with tictoc("nice_actions"):
   ''' do something really nice and tagged as nice_actions '''

I really like this solution because it adds a visual block with the statements that are going to be measured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment