Timer#

class delu.tools.Timer[source]#

Bases: object

A simple pickle-friendly timer for measuring execution time.

Timer is applicable to both long-running activies (e.g. a whole program) and limited code regions (e.g. training steps).

  • Timer can be paused/resumed to measure execution time of only relevant activities.

  • Timer can be used as a context manager.

  • Timer is pickle-friendly and can be saved to / loaded from a checkpoint.

  • Timer can report the elapsed time as a human-readable string with print, str, format and f-strings.

Note

Usage

The common setup for all examples:

>>> from time import sleep
>>>
>>> def train_epoch(): sleep(0.001)
>>> def evaluate_epoch(): sleep(0.001)

Measuring the execution time of a training loop:

>>> # Initially, the timer is not running.
>>> timer = delu.tools.Timer()
>>> assert not timer.is_running
>>>
>>> # Run the timer.
>>> timer.run()
>>> assert timer.is_running
>>>
>>> for epoch in range(100):
...     train_epoch()
...     evaluate_epoch()
>>> duration = timer.elapsed()

Same as above, but using a context:

Important

When entering the context, the timer must not be running.

>>> # Or simply `with delu.tools.Timer() as timer:`
>>> timer = delu.tools.Timer()
>>> with timer:
...     # On enter, timer.run() is called.
...     assert timer.is_running
...     for epoch in range(100):
...         train_epoch()
...         evaluate_epoch()
...
>>> # On exit, timer.pause() is called.
>>> assert not timer.is_running
>>> duration = timer.elapsed()

Measuring the execution time only of the training activity:

>>> timer = delu.tools.Timer()
>>> for epoch in range(100):
...     timer.run()       # Start/resume the timer.
...     train_epoch()     # Recorded.
...     timer.pause()     # Pause the timer.
...
...     evaluate_epoch()  # Not recorded.
>>> total_training_duration = timer.elapsed()

Same as above, but using a context:

>>> timer = delu.tools.Timer()
>>> for epoch in range(100):
...     with timer:
...         train_epoch()
...     evaluate_epoch()
>>> total_training_duration = timer.elapsed()

Using the timer as a global timer (the difference with using time.perf_counter directly is that the start variable below can be safely saved to / loaded from a checkpoint):

>>> timer = delu.tools.Timer()
>>> timer.run()
>>> ...  # Other activities.
>>> start = timer.elapsed()
>>> sleep(0.001)
>>> end = timer.elapsed()
>>> duration = end - start

Resetting the timer:

>>> timer.reset()
>>> assert not timer.is_running
>>> timer.elapsed() == 0.0
True

Reporting the elapsed time in a human-readable format (the default format is the same as for datetime.timedelta: [days "days" if >0] hours:minutes:seconds[.microseconds if >0]):

>>> timer = delu.tools.Timer()
>>> timer.run()
>>> sleep(2.0)  # Sleep for two seconds.
>>> timer.pause()
>>>
>>> # print(timer) also works
>>> str(timer) == format(timer) == f'{timer}'
>>>
>>> # Two seconds plus epsilon:
>>> str(timer).startswith('0:00:02.')
True
>>> f'{timer:%Hh %Mm %Ss}'
'00h 00m 02s'
>>> format(timer, '%Hh %Mm %Ss')
'00h 00m 02s'

A timer can be saved with torch.save/pickle.dump and loaded with torch.load/pickle.load together with other objects:

>>> import tempfile
>>>
>>> model = nn.Linear(1, 1)
>>> timer = delu.tools.Timer()
>>> timer.run()
>>> sleep(0.001)
>>> ...
>>> with tempfile.TemporaryDirectory() as tmpdir:
...     path = f'{tmpdir}/checkpoint.pt'
...     torch.save(
...         {'model': model.state_dict(), 'timer': timer},
...         path,
...     )
...     ...
...     checkpoint = torch.load(path)
...     model.load_state_dict(checkpoint['model'])
...     # The just loaded timer is on pause,
...     # so it must be explicitly resumed.
...     timer = checkpoint['timer']
...     assert not timer.is_running
...     timer.run()

Additional technical examples:

>>> # Implementing a pause context.
>>> from contextlib import ExitStack
>>>
>>> timer = delu.tools.Timer()
>>> timer.run()
>>> ...
>>> timer.pause()
>>> with ExitStack() as stack:
...     # Call `timer.run()` on exit.
...     stack.callback(timer.run)
...     ...  # Some activity which is not recorded by the timer.
>>> timer.is_running
True
__enter__() Timer[source]#

Measure time within a context.

The timer must not be running when entering the context.

  • On enter, Timer.run is called regardless of the current state.

  • On exit, Timer.pause is called regardless of the current state.

__exit__(*args) bool[source]#

Leave the context and pause the timer.

__format__(format_spec: str, /) str[source]#

Format the time elapsed since the start in a human-readable string.

This is a shortcut for time.strftime(format_str, time.gmtime(self())).

Parameters:

format_str – the format string passed to time.strftime.

Returns:

the filled format_str.

Usage

>>> # xdoctest: +SKIP
>>> timer = delu.tools.Timer()
>>> # Let's say that exactly 3661 seconds have passed.
>>> assert format(timer, '%Hh %Mm %Ss') == '01h 01m 01s'
__init__() None[source]#

Args:

__setstate__(state: Dict[str, Any]) None[source]#

Load the state.

A time with just loaded state is not running (basically, it is a freshly created timer which stores the elapsed time from the loaded state).

__str__() str[source]#

Return str(self).

elapsed() float[source]#

Get the time elapsed.

The elapsed time is the time (in seconds) passed since the first .run() call up to the .elapsed() call, minus pauses.

Returns:

The elapsed time.

property is_running: bool#

Check if the timer is running.

pause() None[source]#

Pause the timer.

If the timer is running, the method pauses the timer. If the timer was never .run() or is already on pause, the method does nothing.

reset() None[source]#

Reset the timer completely.

To start using the instance again after resetting, the timer must be explicitly run with Timer.run.

run() None[source]#

Start/resume the timer.

If the timer is on pause, the method resumes the timer. If the timer is running, the method does nothing.