Rewrite the PEP 418

* Rename time.steady() to time.monotonic(), again
 * time.monotonic() does not fallback to the system clock anymore
 * Drop time.perf_counter() function
This commit is contained in:
Victor Stinner 2012-04-04 02:57:55 +02:00
parent f44aa119d8
commit acaec37446
1 changed files with 86 additions and 127 deletions

View File

@ -1,5 +1,5 @@
PEP: 418
Title: Add steady and high-resolution time functions
Title: Add a monotonic time functions
Version: $Revision$
Last-Modified: $Date$
Author: Victor Stinner <victor.stinner@gmail.com>
@ -13,8 +13,7 @@ Python-Version: 3.3
Abstract
========
Add time.steady(), time.perf_counter(), time.get_clock_info(name) functions to
Python 3.3.
Add time.monotonic() and time.get_clock_info(name) functions to Python 3.3.
Rationale
@ -25,8 +24,9 @@ Use cases:
* Display the current time to a human (e.g. display a calendar or draw
a wall clock): use system clock, i.e. time.time() or
datetime.datetime.now().
* Benchmark, profiling: time.perf_counter().
* Event scheduler, timeout: time.steady().
* Event scheduler, timeout: time.monotonic().
* Benchmark, profiling: time.clock() on Windows, time.monotonic(),
or fallback to time.time()
Functions
@ -35,8 +35,7 @@ Functions
To fulfill the use cases, the functions' properties are:
* time.time(): system clock, "wall clock".
* time.perf_counter(): clock with the best accuracy.
* time.steady(): steady clock, should be monotonic
* time.monotonic(): monotonic clock
* time.get_clock_info(name): get information on the specified time function
@ -78,129 +77,75 @@ Pseudo-code [#pseudo]_::
return _time.time()
time.steady()
-------------
time.monotonic()
----------------
Steady clock. Use a monotonic clock, or falls back to the system clock. Its
rate may be adjusted by NTP. The reference point of the returned value is
undefined so only the difference of consecutive calls is valid.
Use time.get_clock_info('steady')['is_monotonic'] to check if the clock
monotonic or not.
Monotonic clock, cannot go backward. Its rate may be adjusted by NTP. The
reference point of the returned value is undefined so only the difference of
consecutive calls is valid.
The elapsed time may or may not include time the system spends in
sleep or hibernation; this depends on the operating system.
Availability: Windows, Mac OS X, Unix.
Pseudo-code [#pseudo]_::
if os.name == 'nt':
# GetTickCount64() requires Windows Vista, Server 2008 or later
if hasattr(time, '_GetTickCount64'):
def steady():
def monotonic():
return _time.GetTickCount64() * 1e-3
else:
def steady():
def monotonic():
ticks = _time.GetTickCount()
if ticks < steady.last:
if ticks < monotonic.last:
# Integer overflow detected
steady.delta += 2**32
steady.last = ticks
return (ticks + steady.delta) * 1e-3
steady.last = 0
steady.delta = 0
monotonic.delta += 2**32
monotonic.last = ticks
return (ticks + monotonic.delta) * 1e-3
monotonic.last = 0
monotonic.delta = 0
elif os.name == 'mac':
def steady():
if steady.factor is None:
def monotonic():
if monotonic.factor is None:
factor = _time.mach_timebase_info()
steady.factor = timebase[0] / timebase[1]
return _time.mach_absolute_time() * steady.factor
steady.factor = None
monotonic.factor = timebase[0] / timebase[1]
return _time.mach_absolute_time() * monotonic.factor
monotonic.factor = None
elif os.name.startswith('sunos'):
def steady():
if steady.use_clock_highres:
elif hasattr(time, "clock_gettime"):
def monotonic():
if monotonic.use_clock_highres:
try:
time.clock_gettime(time.CLOCK_HIGHRES)
except OSError:
steady.use_clock_highres = False
if steady.use_gethrtime:
try:
return time.gethrtime()
except OSError:
steady.use_gethrtime = False
return time.time()
steady.use_clock_highres = (hasattr(time, 'clock_gettime')
monotonic.use_clock_highres = False
return time.clock_gettime(time.CLOCK_MONOTONIC)
monotonic.use_clock_highres = (hasattr(time, 'clock_gettime')
and hasattr(time, 'CLOCK_HIGHRES'))
steady.use_gethrtime = True
elif hasattr(time, "clock_gettime"):
def steady():
while steady.clocks:
try:
clk_id = steady.clocks[0]
return time.clock_gettime(clk_id)
except OSError:
del steady.clocks[0]
return time.time()
steady.clocks = []
if hasattr(time, 'CLOCK_HIGHRES'):
steady.clocks.append(time.CLOCK_HIGHRES)
steady.clocks.append(time.CLOCK_MONOTONIC)
else:
def steady():
return time.time()
On Windows, QueryPerformanceCounter() is not used even though it has a
better resolution than GetTickCount(). It is not reliable and has too
many issues.
On Windows, QueryPerformanceCounter() is not used even though it has a better
accuracy than GetTickCount(). It is not reliable and has too many issues.
.. note::
time.steady() detects GetTickCount() integer overflow (32 bits,
roll-over after 49.7 days): it increases a delta by 2\ :sup:`32`
each time than an overflow is detected. The delta is stored in the
process-local state and so the value of time.steady() may be
different in two Python processes.
time.monotonic() detects GetTickCount() integer overflow (32 bits, roll-over
after 49.7 days): it increases a delta by 2\ :sup:`32` each time than an
overflow is detected. The delta is stored in the process-local state and so
the value of time.monotonic() may be different in two Python processes
running for more than 49 days.
time.perf_counter()
-------------------
Clock with the best available resolution.
It is available on all platforms and cannot fail.
Pseudo-code::
def perf_counter():
if perf_counter.use_performance_counter:
try:
return _time.QueryPerformanceCounter()
except OSError:
# QueryPerformanceFrequency() may fail, if the installed
# hardware does not support a high-resolution performance
# counter for example
perf_counter.use_performance_counter = False
if perf_counter.use_steady:
# Monotonic clock is preferred over system clock
try:
return time.steady()
except OSError:
perf_counter.use_steady = False
return time.time()
perf_counter.use_performance_counter = (os.name == 'nt')
perf_counter.use_steady = hasattr(time, 'steady')
time.get_clock_info(name)
-------------------------
Get information on the specified clock. Supported clocks:
* "clock": time.clock()
* "perf_counter": time.perf_counter()
* "steady": time.steady()
* "monotonic": time.monotonic()
* "time": time.time()
Return a dictionary with the following keys:
@ -264,8 +209,8 @@ Hardware clocks
32,768 Hz
NTP adjusted
============
NTP adjustment
==============
NTP has diffent methods to adjust a clock:
@ -655,10 +600,6 @@ clock_getres(). It looks like Linux does not implement clock_getres() and
always return 1 nanosecond. For GetProcessTimes(), the accuracy is read using
GetSystemTimeAdjustment().
Python source code includes a portable library to get the process time:
`Tools/pybench/systimes.py
<http://hg.python.org/cpython/file/tip/Tools/pybench/systimes.py>`_.
Functions
^^^^^^^^^
@ -793,27 +734,17 @@ Read also the `time(7) manual page
Alternatives: API design
========================
Other names for new functions
-----------------------------
Other names for time.monotonic()
--------------------------------
time.perf_counter():
* time.highres()
* time.hires(): "hires" can be read as "to hire" as in "he hires a car
to go on holiday", rather than a "HIgh-RESolution clock".
* time.timer(): "it would be too easy to confuse with (or misspell as)
time.time()"
time.steady():
* time.monotonic(): QueryPerformanceCounter() is monotonic but it is not used
by time.steady() because it is not steady, and it is surprising to have to
check for time.get_clock_info('monotonic')['is_monotonic'].
* time.try_monotonic(): it is a clear and obvious solution for the
use-case of "I prefer the monotonic clock, if it is available,
otherwise I'll take my chances with a best-effect clock."
* time.steady()
* time.wallclock(): it is not the system time aka the "wall clock", but
a monotonic clock with an unspecified starting point
* time.seconds()
* time.counter()
The name "time.try_monotonic()" was also proposed when time.monotonic() was
falling back to the system clock when no monotonic clock was available.
Only expose operating system clocks
@ -824,14 +755,18 @@ approach is to only expose operating system clocks. time.clock_gettime() and
related clock identifiers were already added to Python 3.3 for example.
Don't fallback on system clock
------------------------------
Fallback to system clock
------------------------
time.monotonic() is always a monotonic clock and is only available if the
operating system provides a monotonic clock.
If no monotonic clock is available, time.monotonic() falls back to the system
clock.
time.perf_counter() is only available if the operating system provides a clock
with a high resolution (e.g. at least a microsecond or better).
Issues:
* It is hard to define correctly such function in the documentation: is it
monotonic? is it steady? is it adjusted?
* Some user want to decide what to do when no monotonic clock is available:
use another clock, display an error, or do something else
One function choosing the clock from a list of constrains
@ -846,7 +781,8 @@ One function choosing the clock from a list of constrains
time.get_clock() returns None if the clock is found and so calls can be chained
using the or operator. Example::
func = time.get_clock(time.MONOTONIC) or time.get_clock(time.STEADY) or time.time()
get_time = time.get_clock(time.MONOTONIC) or time.get_clock(time.STEADY) or time.time()
t = get_time()
Example of flags of system clocks:
@ -854,7 +790,7 @@ Example of flags of system clocks:
* GetTickCount: MONOTONIC | STEADY
* CLOCK_MONOTONIC: MONOTONIC | STEADY (or only MONOTONIC on Linux)
* CLOCK_MONOTONIC_RAW: MONOTONIC | STEADY
* gettimeofday(): (none)
* gettimeofday(): (no flag)
One function with a flag: time.monotonic(fallback=True)
@ -908,6 +844,29 @@ Issues of a hacked monotonic function:
see the same clock value
Deferred API: time.perf_counter()
=================================
Python does not provide a portable "performance counter" clock for benchmarking
or profiling. Each tool has to implement its own heuristic to decide which
clock is the best depending on the OS and on which counters are available.
A previous version of the PEP proposed a time.perf_counter() function using
QueryPerformanceCounter() on Windows, time.monotonic(), or falls back to the
system time. This function was not well defined and the idea is deferred.
Proposed names for such function:
* time.hires()
* time.highres()
* time.perf_counter()
* time.timer()
Python source code includes a portable library to get the process time:
`Tools/pybench/systimes.py
<http://hg.python.org/cpython/file/tip/Tools/pybench/systimes.py>`_.
Footnotes
=========