Oh, did I tell you that the `timeit` module is in the standard library? No extra installs needed.
`timeit.timeit` accepts the piece of code you want to time and returns the number of seconds it took to run.
In the REPL, summing the first 10,000 integers takes... 259 secondsβ
That's only because the `timeit` function accepts a keyword argument `number`, that specifies the number of times your code is going to be ran.
The default value is 1,000,000 (one million) which can be quite huge!
If you want to time things that take longer, change the default!
So, when we used the CLI with `python -m timeit ...` and the program said
β2000 loops, ...β
what that meant was that `number` was set to `2000`.
Let's try setting `number` to 2000 ourselves and compare the CLI result with the REPL result π
The REPL clocked 0.51s for 2000 repetitions, giving 255Β΅s to run `sum(range(10_000))`.
255Β΅s is 35% times slower than 188Β΅s!
Why such a big difference?
That's why the CLI includes the β..., best of 5: ...β!
Your PC has many processes running that impact performance...
To try and work around that, the CLI ran the whole experiment a total of 5 times and took the fastest time.
We take the fastest time (and not the mean) because we can interpret that value as the experiment where the other processes interfered less.
Is this making sense, so far?
So, if we want to reproduce the CLI results better, we need to run the REPL experiment 5 times and look for the best result.
Now, the fastest repetition clocked in at 0.393s for 2000 reps, giving ~197Β΅s for the snippet...
197Β΅s is much closer to 188Β΅s!
Alternatively, we can use the `timeit.repeat` method and make use of the `repeat` keyword argument.
The `repeat` method will return a list with all the times, it is then up to you to compute the `min` of that list.
I hope all of this is making sense so far.
Up to this point, you've learned how to use `timeit` from the command line, you've learned about `timeit.timeit(code, number=1000000)` and you've learned about `timeit.repeat(..., repeat=5)`.
Let's explore `timeit.timeit` a bit more.
Suppose you want to see how fast the built-in factorial function, `math.factorial`, is.
For that, let's just use `timeit.timeit` π
Why do we get a `NameError`??
Because `timeit` runs the snippet in a blank environment, akin to a `.py` script that ONLY contains the snippet.
Ah, no problem, this is easy to fix.
Especially if you know that Python π can use semicolons `;` to separate statements, like I've shown you here π
We just need to import the factorial before we use it and then call it...
Problem solved!
But wait!
Now the `timeit` function is _also_ timing the import statement, which is NOT what we want!
That's why the methods `timeit` and `repeat` accept a `setup` keyword argument.
`setup` specifies a piece of code that prepares the environment, e.g. with necessary imports.
Making proper usage of the `setup` keyword argument lets you measure time execution more accurately.
By removing a simple `import` statement, the time measured decreased by 24% and now represents a more accurate measurement.
Nicely done!
Now we want to compare the built-in `math.factorial` to my home-made `fact` function, using `reduce` and `operator.mul`.
First, a quick check showing that my home-made `fact` produces the correct results:
Now we can time the function, we just need to build the proper setup string...
But that's going to be a huge string!
Wouldn't it be nice if you could just tell `timeit` where to look for the `fact` name..?
You can!
That's what the `globals` argument is for π
In general, you can save yourself some trouble if you use the `globals()` Python π built-in, which is a dictionary that contains all the global names that Python knows of that are _not_ built-ins.
This makes it more convenient to specify many objects:
These are just some of the cool things you can do with the standard module `timeit`!
Recap:
π use `python -m timeit <code>` to measure execution time of a simple snippet in the CL
π use `timeit.repeat` to mimic the CLI behaviour
π use `timeit.timeit` for one-off measurements
`.repeat` and `.timeit` accept some keyword arguments:
π `number`: how many times to run the code
π `setup`: set up the environment
π `globals`: dict with names needed (useful: the `globals()` built-in)
π `.repeat` also accepts `repeat` to produce more accurate measurements
I hope you enjoyed this thread, a LOT of effort was put into this!
If you did, follow me (@mathsppblog) for more content like this.
Consider sharing the beginning of the thread to share your support and appreciation for my work and for Python π!
If you would like to level up your Python π game, you should definitely subscribe to my newsletter π¬, to receive gold nuggets of Python knowledge π§
You also get to know, first hand, when I publish my in-depth Python articles on my blog.