You must have encountered this situation: you wrote a piece of code but don’t know how fast it runs? Or you optimized for a long time but can’t provide exact data to prove your optimization is effective. Don’t worry, Python has a super handy tool – <span>time.perf_counter()</span>
, which can help you measure code execution time with nanosecond precision!
What exactly is time.perf_counter()?
<span>time.perf_counter()</span>
is a high-precision stopwatch in Python. It is not an ordinary stopwatch, but a professional timing tool that can measure time with astonishing accuracy.
It has several impressive features:
- 1. Ultra-high precision – Depending on your system, it can be accurate to nanoseconds or microseconds. Imagine, a blink of an eye takes about 300 milliseconds, while this function can measure time 100,000 times faster than a blink!
- 2. System-level counter – It starts counting from a certain point in time (possibly when your computer was turned on) and keeps going, going, going… You can’t reset it, but that’s what makes it reliable.
- 3. Monotonically increasing – This is something I particularly like! Even if your system time is adjusted (like during daylight saving time changes), it remains unaffected and continues to keep accurate time. It’s like a timer that never stops, regardless of what happens outside; it only cares about the passage of time.
When should you use it?
I think of it in almost every scenario where timing is needed:
- 1. Code performance testing – “How fast is this algorithm?” or “Is my optimization really effective?” These questions can be answered clearly with
<span>perf_counter()</span>
. - 2. Identifying performance bottlenecks – Sometimes a program gets stuck somewhere, but you don’t know exactly where it’s slow. Use it to measure different parts, and the bottleneck will be revealed immediately!
- 3. Show off how fast your code is – Hey, who doesn’t want to demonstrate to colleagues that their optimized code is 50% faster than before? Data speaks louder than words!
How to use it? Super simple
Using it is really intuitive, just like using a physical stopwatch: press start, press stop, and then check the results.
import time
# Press the "start" button
start_time = time.perf_counter()
# Do something... like counting to ten million
for i in range(10000000):
pass
# Press the "stop" button
end_time = time.perf_counter()
# See how long it took
elapsed_time = end_time - start_time
print(f"Wow, this code took {elapsed_time:.6f} seconds!")
It’s that simple! No complicated configurations, no strange parameters, just press start, press stop, calculate the difference, and you’re done!
Practical example
Let’s look at a more practical example. Suppose we want to know how accurate Python’s <span>sleep</span>
function is:
import time
def busy():
# Pretend we are doing some super complex calculations
# Actually just slacking off for 2 seconds
time.sleep(2)
# Start timing
start_time = time.perf_counter()
# Call our "complex" function
busy()
# End timing
end_time = time.perf_counter()
# Reveal the truth
elapsed_time = end_time - start_time
print(f"This 'complex' operation took {elapsed_time:.6f} seconds, hmm... about 2 seconds, sleep is quite accurate!")
Run this code, and you’ll find the result is very close to 2 seconds, there might be a slight error, but that’s because the function call itself takes a tiny bit of time.
Why not use time.time()?
I used to always use <span>time.time()</span>
, until one day I discovered <span>perf_counter()</span>
… it’s like upgrading from a mobile stopwatch to a professional athlete’s timer!
Several reasons made me completely abandon <span>time.time()</span>
:
- 1. Huge difference in precision –
<span>time.time()</span>
is usually only accurate to milliseconds, while<span>perf_counter()</span>
can reach nanoseconds. This difference becomes very apparent when measuring a fast-executing function. - 2. Different reliability – If the system time is adjusted (like automatic network time synchronization), the result of
<span>time.time()</span>
will jump, while<span>perf_counter()</span>
remains stable and continues to keep accurate time. - 3. Professional vs amateur –
<span>time.time()</span>
is designed to tell you what time it is, while<span>perf_counter()</span>
is specifically for measuring code execution time. Using the right tool makes a big difference!
Advanced usage
Once you master the basics, we can play with some advanced techniques:
1. Create a timing decorator
This is one of my favorite tricks, write a decorator that can easily add timing functionality to any function:
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
result = func(*args, **kwargs)
finally:
end = time.perf_counter()
print(f"'{func.__name__}' ran for {end - start:.6f} seconds")
return result
return wrapper
@timer
def complex_calculation():
# Pretend this is a super complex AI algorithm
time.sleep(1.5)
return 42 # The ultimate answer to life, the universe, and everything
answer = complex_calculation() # Automatically outputs execution time
2. Code competition
Sometimes I write two different implementations and let them compete in speed:
import time
# Competitor 1: Generator expression
start1 = time.perf_counter()
result1 = sum(i for i in range(10000000))
time1 = time.perf_counter() - start1
# Competitor 2: List comprehension
start2 = time.perf_counter()
result2 = sum([i for i in range(10000000)])
time2 = time.perf_counter() - start2
print(f"Solution 1 took: {time1:.6f} seconds")
print(f"Solution 2 took: {time2:.6f} seconds")
print(f"Difference: {abs(time1 - time2):.6f} seconds")
print(f"The winner is: {'Solution 1' if time1 < time2 else 'Solution 2'}")
3. Context manager timer
Sometimes I get lazy and don’t want to write two lines of code to time, so I use this context manager:
import time
from contextlib import contextmanager
@contextmanager
def timer(description="Some code"):
start = time.perf_counter()
result = None
try:
yield result
finally:
elapsed = time.perf_counter() - start
print(f"'{description}' took: {elapsed:.6f} seconds")
# Super convenient to use
with timer("Sorting 100,000 numbers"):
sorted([5, 3, 1, 4, 2] * 100000)
Comparison with other timing functions
Function | Purpose | Includes sleep time | Precision | Reference point |
<span>time.time()</span> |
Get system timestamp (e.g., for logging) | ✔️ | Low (usually millisecond level) | System time (can be adjusted) |
<span>time.monotonic()</span> |
Monotonically increasing wall clock time measurement | ✔️ | Medium | Fixed point after system startup |
<span>time.process_time()</span> |
Measure CPU time of the process (excluding sleep) | ❌ | Medium | Process startup time |
<span>time.perf_counter()</span> |
High precision, monotonically increasing wall clock time measurement | ✔️ | Highest | Uncertain (only the difference is meaningful) |
Usage tips
Honestly, I’ve stumbled upon some pitfalls during usage, here are a few experiences to share:
- 1. One measurement is unreliable – The computer’s state is always changing, background processes may suddenly become active, so I usually measure 3-5 times and take the average. Sometimes the first measurement result can be particularly outrageous, so it’s advisable to measure multiple times.
- 2. Warm-up is important – Especially for languages like Python that have JIT, the first run and subsequent runs may differ significantly. I usually run a “warm-up” first, and then start the official timing.
- 3. Don’t get too hung up on tiny differences – If two implementations differ by only 0.001 seconds, it may not be worth optimizing. I used to rewrite code for such tiny differences, but now I realize it was a waste of time.
- 4. Environmental factors are important – The same code may run 10 times slower on my old laptop compared to my new desktop. So measurement results are only meaningful in the same environment.
- 5. Only the difference is meaningful: The absolute value returned by
<span>perf_counter()</span>
is meaningless, it can only be used to calculate the difference between two calls. - 6. Alternative: Python 3.7+ provides
<span>perf_counter_ns()</span>
, which directly returns nanosecond values, avoiding floating-point precision issues.
Conclusion
Honestly, <span>time.perf_counter()</span>
has changed the way I write code. I used to only be able to say, “This code should be faster,” but now I can confidently say, “This code is 37.2% faster than before.”
It feels like it has given me a pair of eyes to see code performance. Whether for daily development or deep optimization, this little function is my trusty assistant. The best part is, it’s super easy to use, and you can measure code performance effortlessly.
If you’re still using <span>time.time()</span>
or other methods to measure code execution time, I strongly recommend trying <span>perf_counter()</span>
. Trust me, you’ll love it! After all, in the pursuit of code performance, precise measurement is the first step, and <span>perf_counter()</span>
is the best companion for that first step.