|
|
""" |
|
|
Utility functions for timing code blocks |
|
|
""" |
|
|
|
|
|
import time |
|
|
from contextlib import ContextDecorator |
|
|
|
|
|
import numpy as np |
|
|
|
|
|
|
|
|
class BlockTimeManager: |
|
|
""" |
|
|
Manages a collection of timers and their formatting options. |
|
|
|
|
|
This class serves as a central registry for Timer objects, allowing them to be |
|
|
accessed by name and maintaining their formatting preferences. |
|
|
|
|
|
Attributes: |
|
|
timers (dict): Dictionary mapping timer names to Timer objects |
|
|
timer_fmts (dict): Dictionary mapping timer names to their display formats |
|
|
window_size (int): Default window size for calculating windowed averages |
|
|
buf_size (int): Default buffer size for storing timing measurements |
|
|
""" |
|
|
|
|
|
def __init__(self, window_size=10, buf_size=100000): |
|
|
self.timers = dict() |
|
|
self.timer_fmts = dict() |
|
|
self.window_size = window_size |
|
|
self.buf_size = buf_size |
|
|
|
|
|
|
|
|
btm = BlockTimeManager(window_size=100000) |
|
|
|
|
|
|
|
|
class Timer: |
|
|
""" |
|
|
Core timing class that tracks execution times. |
|
|
|
|
|
This class provides the fundamental timing functionality, storing timing measurements |
|
|
and calculating various statistics. |
|
|
|
|
|
Attributes: |
|
|
name (str): Identifier for this timer |
|
|
buf_size (int): Maximum number of timing measurements to store |
|
|
window_size (int): Number of most recent measurements to use for windowed statistics |
|
|
measures_arr (numpy.ndarray): Array storing start and end times of measurements |
|
|
current_start (float or None): Start time of current measurement |
|
|
current_end (float or None): End time of current measurement |
|
|
""" |
|
|
|
|
|
def __init__(self, name, window_size, buf_size=100000): |
|
|
self.name = name |
|
|
self.buf_size = buf_size |
|
|
self.window_size = window_size |
|
|
self.init() |
|
|
|
|
|
def init(self): |
|
|
"""Initialize or reset the timer's state.""" |
|
|
self.measures_arr = np.empty((0, 2)) |
|
|
self.current_start = None |
|
|
self.current_end = None |
|
|
|
|
|
def reset(self): |
|
|
"""Reset the timer to its initial state.""" |
|
|
self.init() |
|
|
|
|
|
def tic(self): |
|
|
"""Start a new timing measurement.""" |
|
|
if self.current_start is not None: |
|
|
|
|
|
self.toc() |
|
|
self.current_start = time.perf_counter() |
|
|
|
|
|
def toc(self): |
|
|
"""End the current timing measurement.""" |
|
|
self.current_end = time.perf_counter() |
|
|
self._add_current_measure() |
|
|
|
|
|
def _add_current_measure(self): |
|
|
"""Add the current timing measurement to the measurements array.""" |
|
|
self.measures_arr = np.concatenate( |
|
|
[ |
|
|
np.array([[self.current_start, self.current_end]]), |
|
|
self.measures_arr[: self.buf_size], |
|
|
] |
|
|
) |
|
|
self.current_start = None |
|
|
self.current_end = None |
|
|
|
|
|
@property |
|
|
def avg(self) -> float: |
|
|
"""Calculate the average execution time across all measurements.""" |
|
|
return np.mean(self.measures_arr[:, 1] - self.measures_arr[:, 0]) |
|
|
|
|
|
@property |
|
|
def wavg(self) -> float: |
|
|
"""Calculate the windowed average execution time using the most recent measurements.""" |
|
|
return np.mean( |
|
|
self.measures_arr[: self.window_size, 1] |
|
|
- self.measures_arr[: self.window_size, 0] |
|
|
) |
|
|
|
|
|
@property |
|
|
def max(self) -> float: |
|
|
"""Return the maximum execution time.""" |
|
|
return np.max(self.measures_arr[:, 1] - self.measures_arr[:, 0]) |
|
|
|
|
|
@property |
|
|
def min(self) -> float: |
|
|
"""Return the minimum execution time.""" |
|
|
return np.min(self.measures_arr[:, 1] - self.measures_arr[:, 0]) |
|
|
|
|
|
@property |
|
|
def total(self) -> float: |
|
|
"""Return the total execution time across all measurements.""" |
|
|
return np.sum(self.measures_arr[:, 1] - self.measures_arr[:, 0]) |
|
|
|
|
|
@property |
|
|
def latest(self) -> float: |
|
|
"""Return the most recent execution time.""" |
|
|
return self.measures_arr[0, 1] - self.measures_arr[0, 0] |
|
|
|
|
|
@property |
|
|
def median(self) -> float: |
|
|
"""Return the median execution time.""" |
|
|
return np.median(self.measures_arr[:, 1] - self.measures_arr[:, 0]) |
|
|
|
|
|
@property |
|
|
def var(self) -> float: |
|
|
"""Return the variance of execution times.""" |
|
|
return np.var(self.measures_arr[:, 1] - self.measures_arr[:, 0]) |
|
|
|
|
|
|
|
|
class BlockTimer(ContextDecorator): |
|
|
""" |
|
|
A context manager and decorator for timing code blocks. |
|
|
|
|
|
This class provides a convenient interface for timing code execution, either as a |
|
|
context manager (with statement) or as a decorator. It uses the Timer class for |
|
|
the actual timing functionality. |
|
|
|
|
|
Attributes: |
|
|
name (str): Identifier for this timer |
|
|
fmt (str or None): Format string for displaying timing information |
|
|
timer (Timer): The underlying Timer object |
|
|
num_calls (int): Number of times this timer has been called |
|
|
""" |
|
|
|
|
|
@staticmethod |
|
|
def timers(): |
|
|
"""Return a list of all registered timer names.""" |
|
|
return list(btm.timers.keys()) |
|
|
|
|
|
def __init__(self, name, fmt=None, window_size=100): |
|
|
self.name = name |
|
|
if name in btm.timers: |
|
|
self.timer = btm.timers[name] |
|
|
|
|
|
self.fmt = fmt if fmt is not None else btm.timer_fmts[name] |
|
|
else: |
|
|
self.timer = Timer(name, btm.window_size, btm.buf_size) |
|
|
btm.timers[name] = self.timer |
|
|
btm.timer_fmts[name] = fmt |
|
|
self.timer.window_size = window_size |
|
|
self._default_fmt = "[{name}] num: {num} latest: {latest:.4f} --wind_avg: {wavg:.4f} -- avg: {avg:.4f} --var: {var:.4f} -- total: {total:.4f}" |
|
|
if fmt == "default": |
|
|
self.fmt = self._default_fmt |
|
|
|
|
|
else: |
|
|
self.fmt = None |
|
|
|
|
|
self.num_calls = 0 |
|
|
|
|
|
def __enter__(self) -> "Timer": |
|
|
"""Start timing when entering a context.""" |
|
|
self.tic() |
|
|
return self |
|
|
|
|
|
def __exit__(self, *args): |
|
|
"""End timing when exiting a context and optionally display results.""" |
|
|
self.toc() |
|
|
if self.fmt is not None: |
|
|
print(str(self)) |
|
|
|
|
|
def __str__(self) -> str: |
|
|
"""Return a string representation of the timer.""" |
|
|
return self.display() |
|
|
|
|
|
def reset(self): |
|
|
"""Reset the timer and call counter.""" |
|
|
self.timer.reset() |
|
|
self.num_calls = 0 |
|
|
|
|
|
def display(self, fmt=None): |
|
|
""" |
|
|
Format and return timing information. |
|
|
|
|
|
Args: |
|
|
fmt (str, optional): Format string to use. If None, uses the timer's format. |
|
|
|
|
|
Returns: |
|
|
str: Formatted timing information |
|
|
""" |
|
|
if fmt is None: |
|
|
if self.fmt is not None: |
|
|
fmt = self.fmt |
|
|
else: |
|
|
fmt = self._default_fmt |
|
|
return fmt.format( |
|
|
name=self.name, |
|
|
num=self.num_calls, |
|
|
latest=self.latest, |
|
|
wavg=self.wavg, |
|
|
avg=self.avg, |
|
|
var=self.var, |
|
|
total=self.total, |
|
|
) |
|
|
|
|
|
def tic(self): |
|
|
"""Start a new timing measurement and increment the call counter.""" |
|
|
self.timer.tic() |
|
|
self.num_calls += 1 |
|
|
|
|
|
def toc(self, display=False): |
|
|
""" |
|
|
End the current timing measurement. |
|
|
|
|
|
Args: |
|
|
display (bool): Whether to return a formatted display string |
|
|
|
|
|
Returns: |
|
|
str or None: Formatted timing information if display is True |
|
|
""" |
|
|
self.timer.toc() |
|
|
if display: |
|
|
return self.display() |
|
|
|
|
|
@property |
|
|
def latest(self) -> float: |
|
|
"""Return the most recent execution time.""" |
|
|
return self.timer.latest |
|
|
|
|
|
@property |
|
|
def avg(self) -> float: |
|
|
"""Return the average execution time.""" |
|
|
return self.timer.avg |
|
|
|
|
|
@property |
|
|
def wavg(self) -> float: |
|
|
"""Return the windowed average execution time.""" |
|
|
return self.timer.wavg |
|
|
|
|
|
@property |
|
|
def max(self) -> float: |
|
|
"""Return the maximum execution time.""" |
|
|
return self.timer.max |
|
|
|
|
|
@property |
|
|
def min(self) -> float: |
|
|
"""Return the minimum execution time.""" |
|
|
return self.timer.min |
|
|
|
|
|
@property |
|
|
def total(self) -> float: |
|
|
"""Return the total execution time.""" |
|
|
return self.timer.total |
|
|
|
|
|
@property |
|
|
def median(self) -> float: |
|
|
"""Return the median execution time.""" |
|
|
return self.timer.median |
|
|
|
|
|
@property |
|
|
def var(self) -> float: |
|
|
"""Return the variance of execution times.""" |
|
|
return self.timer.var |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
@BlockTimer("fct", "default") |
|
|
def fct(bobo): |
|
|
time.sleep(0.5) |
|
|
|
|
|
fct(2) |
|
|
|
|
|
for i in range(10): |
|
|
with BlockTimer("affe", "default"): |
|
|
time.sleep(0.1) |
|
|
for i in range(1000): |
|
|
with BlockTimer("test", None): |
|
|
time.sleep(0.001) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(BlockTimer("test")) |
|
|
BlockTimer("test").tic() |
|
|
BlockTimer("t2", "default").tic() |
|
|
time.sleep(0.4) |
|
|
print(BlockTimer("t2").toc(True)) |
|
|
|
|
|
time.sleep(0.4) |
|
|
print(BlockTimer("test").toc(True)) |
|
|
|