python-peps/pep-0418.txt

399 lines
12 KiB
Plaintext
Raw Normal View History

2012-03-26 19:12:03 -04:00
PEP: 418
Title: Add monotonic and high-resolution time functions
2012-03-26 19:12:03 -04:00
Version: $Revision$
Last-Modified: $Date$
Author: Victor Stinner <victor.stinner@gmail.com>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 26-March-2012
Python-Version: 3.3
Abstract
========
Add time.monotonic(), time.hires() and time.try_monotonic() functions to Python
3.3.
2012-03-26 19:12:03 -04:00
Rationale
=========
Use cases:
* Display the current time to a human (e.g. display a calendar or draw a wall
clock): use system clock. time.time() or datetime.datetime.now()
2012-03-26 19:12:03 -04:00
* Implement a timeout: use monotonic clock, or fallback to the clock with
the highest resolution. time.try_monotonic()
* Benchmark and profiling: time.hires(), with a manual fallback to time.time()
* Event scheduler: time.monotonic()
2012-03-26 19:12:03 -04:00
time.try_monotonic() tries to use a monotonic clock, but it falls back to a
2012-03-26 19:12:03 -04:00
non-monotonic clock if no monotonic clock is available or if reading a
monotonic clock failed.
The pybench benchmark tool supports the following clocks: time.clock(),
time.time() and systimes.processtime(). By default, it uses time.clock() on
Windows, and time.time() otherwise. It is possible to choose the clock using
the --timer command line option.
Which clock?
* System time: pthread_mutex_timedlock(), pthread_cond_wait() (CLOCK_REALTIME)
* Monotonic: clock_nanosleep() (CLOCK_MONOTONIC)
Timeouts:
* threading.Lock.wait()
* select.select()
* time.sleep()
* subprocess.Popen.communicate()
* etc.
Functions
=========
time.time()
-----------
The system time is the "wall clock". It can be set manually by the system
administrator or automatically by a NTP daemon. It can jump backward and
forward, and is not monotonic.
It is avaialble on all platforms and cannot fail.
Pseudo-code::
if os.name == "nt":
def time():
return _time.GetSystemTimeAsFileTime()
elif hasattr(time, "clock_gettime") and hasattr(time, "CLOCK_REALTIME"):
def time():
# resolution = 1 nanosecond
return time.clock_gettime(time.CLOCK_REALTIME)
else:
def time():
if hasattr(_time, "gettimeofday"):
try:
# resolution = 1 microsecond
return _time.gettimeofday()
except OSError:
pass
if hasattr(_time, "ftime"):
# resolution = 1 millisecond
return _time.ftime()
else:
# resolution = 1 second
return _time.time()
time.monotonic()
----------------
Monotonic clock advancing at a monotonic rate relative to real time. It
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.
It is not avaialble on all platforms and raise an OSError on error.
The monotonic clock may stop while the system is suspended.
Pseudo-code::
if os.name == 'nt':
if hasattr(time, '_GetTickCount64'):
monotonic = _time.GetTickCount64
else:
def monotonic():
ticks = _time.GetTickCount()
if ticks < monotonic.last:
# Integer overflow detected
monotonic.delta += 2**32
monotonic.last = ticks
return ticks + monotonic.delta
monotonic.last = 0
monotonic.delta = 0
if os.name == 'mac':
def monotonic():
if monotonic.factor is None:
factor = _time.mach_timebase_info()
monotonic.factor = timebase[0] / timebase[1]
return _time.mach_absolute_time() * monotonic.factor
monotonic.factor = None
elif hasattr(time, "clock_gettime") and hasattr(time, "CLOCK_MONOTONIC"):
def monotonic():
if monotonic.use_monotonic_raw:
try:
return time.clock_gettime(time.CLOCK_MONOTONIC_RAW)
except OSError:
monotonic.use_monotonic_raw = False
return time.clock_gettime(time.CLOCK_MONOTONIC)
monotonic.use_monotonic_raw = hasattr(time, "CLOCK_MONOTONIC_RAW")
time.hires()
------------
High-resolution clock. It has an unspecified starting point and may be
adjusted. The clock starting point changes when the system is resumed after
being suspended.
Pseudo-code::
if os.name == 'nt':
def hires():
return _time.QueryPerformanceCounter()
elif hasattr(time, "monotonic"):
hires = time.monotonic
elif hasattr(time, "clock_gettime") and hasattr(time, "CLOCK_REALTIME"):
def hires():
return time.clock_gettime(time.CLOCK_REALTIME)
It is not available on all platforms and may raise an OSError.
time.try_monotonic()
--------------------
This clock advances at a steady rate relative to real time. It may be adjusted.
The reference point of the returned value is undefined so only the difference
of consecutive calls is valid.
Pseudo-code::
def try_monotonic():
if try_monotonic.use_monotonic:
try:
return time.monotonic()
except (AttributeError, OSError):
try_monotonic.use_monotonic = False
if try_monotonic.use_hires:
try:
return time.hires()
except (AttributeError, OSError):
try_monotonic.use_hires = False
return time.time()
try_monotonic.use_monotonic = True
try_monotonic.use_hires = True
If available, a monotonic clock is used. The function falls back to another
clock if the monotonic clock failed or is not available.
This function cannot fail.
2012-03-26 19:12:03 -04:00
Clocks
======
Monotonic
---------
* mach_absolute_time(): Mac OS X provides a monotonic clock:
mach_absolute_time(). mach_timebase_info() provides a fraction to convert it
to a number of nanoseconds. According to the documentation,
mach_timebase_info() is always equals to one and does never fail, even if
the function may fail according to its prototype.
* clock_gettime(CLOCK_MONOTONIC): Clock that cannot be set and represents
monotonic time since some unspecified starting point.
* clock_gettime(CLOCK_MONOTONIC_RAW), since Linux 2.6.28; Linux-specific:
Similar to CLOCK_MONOTONIC, but provides access to a raw hardware-based time
that is not subject to NTP adjustments.
* Windows: GetTickCount(), GetTickCount64().
CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW clocks cannot be set.
On Linux, NTP may adjust CLOCK_MONOTONIC rate, but not jump backward. If
available, CLOCK_MONOTONIC_RAW should be used instead of CLOCK_MONOTONIC to
avoid the NTP adjustement. CLOCK_MONOTONIC stops while the machine is
suspended.
Resolution:
* mach_absolute_time(): 1 nanosecond
* CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW: be read using clock_getres().
1 nanosecond on Linux for example.
* GetTickCount(), GetTickCount64(): 1 millisecond
May fail?
* mach_absolute_time() cannot fail. According to the documentation,
mach_timebase_info() does never fail, even if the function has a return
value.
* clock_gettime() can fail if the system does not support the specified clock,
whereas the standard C library supports it. For example, CLOCK_MONOTONIC_RAW
requires a kernel version 2.6.28 or later.
* GetTickCount() and GetTickCount64() cannot fail
Note: clock_gettime() requires to link the program with the realtime ("rt") library.
Note: GetTickCount64() was added to Windows Vista and Windows Server 2008.
System time
-----------
System time on Windows
^^^^^^^^^^^^^^^^^^^^^^
The system time can be read using GetSystemTimeAsFileTime(), ftime() and
time().
The system time resolution can be read using GetSystemTimeAdjustment(). The
accurary is usually between 0.5 millisecond and 15 milliseconds. Resolution:
* GetSystemTimeAsFileTime(): 100 nanoseconds
* ftime(): 1 millisecond
* time(): 1 second
The system time can be set using SetSystemTime().
System time on UNIX
^^^^^^^^^^^^^^^^^^^
gettimeofday(), ftime(), time() and clock_settime(CLOCK_REALTIME) return the
system clock.
Resolution:
* clock_settime(): clock_getres(CLOCK_REALTIME), 1 nanosecond on Linux
* gettimeofday(): 1 microsecond
* ftime(): 1 millisecond
* time(): 1 second
The system time can be set using settimeofday() or clock_settime(CLOCK_REALTIME).
Process and thread time
-----------------------
The process and thread time cannot be set. They are not monotonic: the clocks
stop while the process/thread is idle.
Process
^^^^^^^
* Windows: GetProcessTimes()
* clock_gettime(CLOCK_PROCESS_CPUTIME_ID): High-resolution per-process timer from the CPU.
* clock():
* Windows: The elapsed wall-clock time since the start of the process
(elapsed time in seconds times CLOCKS_PER_SEC). It can fail.
* UNIX: returns an approximation of processor time used by the program.
* times()
* getrusage(): ru_utime and ru_stime fields
Resolution:
* clock() rate is CLOCKS_PER_SEC. It was called CLK_TCK in Microsoft C before
6.0. On Linux 3, clock() has a resolution of 1 microsecond
* The clock resolution can be read using clock_getres().
clock_getres(CLOCK_REALTIME) is 1 nanosecond on Linux
* GetProcessTimes(): call GetSystemTimeAdjustment()
Thread
^^^^^^
* Windows: GetThreadTimes()
* clock_gettime(CLOCK_THREAD_CPUTIME_ID): Thread-specific CPU-time clock.
Resolution:
* CLOCK_THREAD_CPUTIME_ID: call clock_getres(). 1 nanosecond on Linux.
* GetThreadTimes(): call GetSystemTimeAdjustment()
See also pthread_getcpuclockid().
QueryPerformanceCounter
-----------------------
Windows provides a high-resolution performance counter:
QueryPerformanceCounter(). Its frequency can be retrieved using
QueryPerformanceFrequency().
On Windows XP, QueryPerformanceFrequency() is the processor frequency and
QueryPerformanceCounter() is the TSC of the current processor. Windows XP
had a bug (see `KB896256 <http://support.microsoft.com/?id=896256>`_): on a
multiprocessor computer, QueryPerformanceCounter() returned a different value
for each processor.
QueryPerformanceCounter() is not monotonic.
QueryPerformanceFrequency() fails if the installed hardware does not support a
high-resolution performance counter.
QueryPerformanceFrequency() should only be called once: the frequency will not
change while the system is running.
QueryUnbiasedInterruptTime
--------------------------
Gets the current unbiased interrupt time from the biased interrupt time and the
current sleep bias amount. This time is not affected by power management sleep
transitions.
Is it monotonic?
QueryUnbiasedInterruptTime() was introduced in Windows 7.
Alternatives: API design
========================
2012-03-26 19:12:03 -04:00
One function with a flag: time.try_monotonic(strict=False)
----------------------------------------------------------
2012-03-26 19:12:03 -04:00
* time.try_monotonic(strict=False) falls back to another clock if no monotonic clock
2012-03-26 19:12:03 -04:00
is not available or does not work, but it does never fail.
* time.try_monotonic(strict=True) raises OSError if monotonic clock fails or
2012-03-26 19:12:03 -04:00
NotImplementedError if the system does not provide a monotonic clock
"A keyword argument that gets passed as a constant in the caller is usually
poor API."
Raising NotImplementedError for a function is something uncommon in Python and
should be avoided.
2012-03-26 19:12:03 -04:00
One function, no flag
---------------------
time.try_monotonic() returns (time: float, is_monotonic: bool).
2012-03-26 19:12:03 -04:00
An alternative is to use a function attribute: time.try_monotonic.monotonic. The
attribute value would be None before the first call to time.try_monotonic().
2012-03-26 19:12:03 -04:00
Working around operating system bugs?
=====================================
2012-03-26 19:12:03 -04:00
Should Python ensure manually that a monotonic clock is truly monotonic by
computing the maximum with the clock value and the previous value?
* Virtual machines provide less reliable clocks.
* QueryPerformanceCounter() had a bug in 2006 on multiprocessor computers
Links
=====
2012-03-26 20:19:00 -04:00
* `Issue #12822: NewGIL should use CLOCK_MONOTONIC if possible.
<http://bugs.python.org/issue12822>`_
* `Issue #14222: Use time.steady() to implement timeout
<http://bugs.python.org/issue14222>`_
* `Issue #14397: Use GetTickCount/GetTickCount64 instead of QueryPerformanceCounter for monotonic clock
<http://bugs.python.org/issue14397>`_
* `python-monotonic-time
<http://code.google.com/p/python-monotonic-time/>`_
(`github <https://github.com/gavinbeatty/python-monotonic-time>`_)
* `Qt library: QElapsedTimer
<http://qt-project.org/doc/qt-4.8/qelapsedtimer.html>`_
2012-03-26 19:12:03 -04:00
* `Windows: Game Timing and Multicore Processors
<http://msdn.microsoft.com/en-us/library/ee417693.aspx>`_