python-peps/pep-0418.txt

608 lines
22 KiB
Plaintext

PEP: 418
Title: Add monotonic and high-resolution time functions
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() and time.highres() functions to Python 3.3.
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()
* Benchmark, profiling, timeout: time.highres()
* Event scheduler: time.monotonic()
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. It is not monotonic.
It is available on all platforms and cannot fail.
Pseudo-code [#pseudo]_: ::
if os.name == "nt":
def time():
return _time.GetSystemTimeAsFileTime()
else:
def time():
if hasattr(time, "clock_gettime"):
try:
# resolution = 1 nanosecond
return time.clock_gettime(time.CLOCK_REALTIME)
except OSError:
# CLOCK_REALTIME is not supported (unlikely)
pass
if hasattr(_time, "gettimeofday"):
try:
# resolution = 1 microsecond
return _time.gettimeofday()
except OSError:
# gettimeofday() should not fail
pass
if hasattr(_time, "ftime"):
# resolution = 1 millisecond
return _time.ftime()
else:
# resolution = 1 second
return _time.time()
time.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 available on all platforms and may raise an OSError. It is not
available on GNU/Hurd for example.
The monotonic clock may stop while the system is suspended.
Pseudo-code [#pseudo]_: ::
if os.name == 'nt':
# GetTickCount64() requires Windows Vista, Server 2008 or later
if hasattr(time, '_GetTickCount64'):
_get_tick_count = _time.GetTickCount64
else:
def _get_tick_count():
ticks = _time.GetTickCount()
if ticks < _get_tick_count.last:
# Integer overflow detected
_get_tick_count.delta += 2**32
_get_tick_count.last = ticks
return ticks + _get_tick_count.delta
_get_tick_count.last = 0
_get_tick_count.delta = 0
def monotonic():
if monotonic.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
monotonic.use_performance_counter = False
# Fallback to GetTickCount/GetTickCount64 which has
# a lower resolution
return _get_tick_count()
monotonic.use_performance_counter = True
elif 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 os.name.startswith('sunos'):
def monotonic():
if monotonic.use_clock_highres:
try:
time.clock_gettime(time.CLOCK_HIGHRES)
except OSError:
monotonic.use_clock_highres = False
return time.gethrtime()
monotonic.use_clock_highres = (hasattr(time, 'clock_gettime')
and hasattr(time, 'CLOCK_HIGHRES'))
elif hasattr(time, "clock_gettime"):
def monotonic():
while monotonic.clocks:
try:
clk_id = monotonic.clocks[0]
return time.clock_gettime(clk_id)
except OSError:
# CLOCK_MONOTONIC_RAW requires a Linux kernel >= 2.6.28
del monotonic.clocks[0]
return time.clock_gettime(time.CLOCK_MONOTONIC)
monotonic.clocks = []
if hasattr(time, 'CLOCK_MONOTONIC_RAW'):
monotonic.clocks.append(time.CLOCK_MONOTONIC_RAW)
if hasattr(time, 'CLOCK_HIGHRES'):
monotonic.clocks.append(time.CLOCK_HIGHRES)
.. note::
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
time.monotonic() value may be different in two Python processes.
time.highres()
--------------
High-resolution clock: use a monotonic clock if available, or fallback to the
system time.
It is available on all platforms and cannot fail.
Pseudo-code::
def highres():
if highres.use_monotonic:
try:
return time.monotonic()
except OSError:
highres.use_monotonic = False
return time.time()
highres.use_monotonic = hasattr(time, 'monotonic')
Clocks
======
Monotonic
---------
Summary
^^^^^^^
Table summarizing all monotonic clocks:
========================= =============== ================ ====================
Name Resolution Adjusted by NTP? Action on suspend
========================= =============== ================ ====================
CLOCK_MONOTONIC_RAW 1 ns No Stopped
gethrtime 1 ns No Not stopped
mach_absolute_time() 1 ns No ?
CLOCK_HIGHRES 1 ns No ?
CLOCK_MONOTONIC 1 ns Yes on Linux Stopped on Linux
QueryPerformanceCounter() 0.3 ns - 5 ns No Accuracy issue
GetTickCount[64]() 1 ms - 15 ms No Include suspend time
timeGetTime() 1 ms - 15 ms No ?
========================= =============== ================ ====================
mach_absolute_time
^^^^^^^^^^^^^^^^^^
Mac OS X provides a monotonic clock: mach_absolute_time(). It is based on
absolute elapsed time delta since system boot. It is not adjusted and cannot be
set.
mach_timebase_info() gives a fraction to convert the clock value to a number of
nanoseconds. According to the documentation (`Technical Q&A QA1398
<https://developer.apple.com/library/mac/#qa/qa1398/>`_), mach_timebase_info()
is always equals to one and does never fail, even if the function may fail
according to its prototype.
mach_absolute_time() stops during a sleep on PowerPC CPU, but not on Intel CPU:
`Different behaviour of mach_absolute_time() on i386 / ppc
<http://lists.apple.com/archives/PerfOptimization-dev/2006/Jul/msg00024.html>`_.
mach_absolute_time() has a resolution of 1 nanosecond.
CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW represents monotonic time since some
unspecified starting point. They cannot be set.
Documentation: refer to the manual page of your operating system. Examples:
* `FreeBSD clock_gettime() manual page
<http://www.freebsd.org/cgi/man.cgi?query=clock_gettime>`_
* `Linux clock_gettime() manual page
<http://linux.die.net/man/3/clock_gettime>`_
CLOCK_MONOTONIC is available at least on the following operating systems:
* DragonFly BSD, FreeBSD >= 5.0, OpenBSD, NetBSD
* Linux
* Solaris
The following operating systems don't support CLOCK_MONOTONIC:
* GNU/Hurd (see `open issues/ clock_gettime <http://www.gnu.org/software/hurd/open_issues/clock_gettime.html>`_)
* Mac OS X
* Windows
CLOCK_MONOTONIC_RAW is specific to Linux. It is similar to CLOCK_MONOTONIC, but
provides access to a raw hardware-based time that is not subject to NTP
adjustments. CLOCK_MONOTONIC_RAW requires Linux 2.6.28 or later.
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.
clock_gettime() fails 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.
clock_getres() gives the clock resolution. It is 1 nanosecond on Linux.
.. note::
clock_gettime() requires to link the program to the rt (real-time) library.
Windows: QueryPerformanceCounter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
High-resolution performance counter. It is monotonic.
QueryPerformanceFrequency() gives its frequency.
It has much higher resolution, but has lower long term accuracy than
GetTickCount() and timeGetTime() clocks. For example, it will drift compared to
the low precision clocks.
Documentation:
* `MSDN: QueryPerformanceCounter() documentation
<http://msdn.microsoft.com/en-us/library/windows/desktop/ms644904%28v=vs.85%29.aspx>`_
* `MSDN: QueryPerformanceFrequency() documentation
<http://msdn.microsoft.com/en-us/library/windows/desktop/ms644905%28v=vs.85%29.aspx>`_
Hardware clocks used by QueryPerformanceCounter:
* Windows XP: RDTSC instruction of Intel processors, the clock frequency is
the frequency of the processor (between 200 MHz and 3 GHz, usually greater
than 1 GHz nowadays)
* Windows 2000: ACPI power management timer, frequency = 3,549,545 Hz. It can
be forced through the "/usepmtimer" flag in boot.ini
* Windows 95/98: 8245 PIT chipset, frequency = 1,193,181 Hz
QueryPerformanceFrequency() should only be called once: the frequency will not
change while the system is running. It fails if the installed hardware does not
support a high-resolution performance counter.
QueryPerformanceCounter() cannot be adjusted: `SetSystemTimeAdjustment()
<http://msdn.microsoft.com/en-us/library/windows/desktop/ms724943(v=vs.85).aspx>`_
does only adjust the system time.
Bugs:
* The performance counter value may unexpectedly leap forward because of a
hardware bug, see the `KB274323`_.
* On VirtualBox, QueryPerformanceCounter() does not increment the high part
every time the low part overflows, see `Monotonic timers
<http://code-factor.blogspot.fr/2009/11/monotonic-timers.html>`_ (2009).
* VirtualBox had a bug in its HPET virtualized device:
QueryPerformanceCounter() did jump forward by approx. 42 seconds (`issue
#8707 <https://www.virtualbox.org/ticket/8707>`_).
* Windows XP had a bug (see `KB896256`_): on a multiprocessor computer,
QueryPerformanceCounter() returned a different value for each processor.
The bug was fixed in Windows XP SP2.
.. _KB896256: http://support.microsoft.com/?id=896256
.. _KB274323: http://support.microsoft.com/?id=274323
Windows: GetTickCount(), GetTickCount64()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
GetTickCount() and GetTickCount64() are monotonic, cannot fail and are not
adjusted by SetSystemTimeAdjustment(). MSDN documentation:
`GetTickCount() <http://msdn.microsoft.com/en-us/library/windows/desktop/ms724408(v=vs.85).aspx>`_,
`GetTickCount64() <http://msdn.microsoft.com/en-us/library/windows/desktop/ms724411(v=vs.85).aspx>`_.
The elapsed time retrieved by GetTickCount or GetTickCount64 includes time the
system spends in sleep or hibernation.
GetTickCount64() was added to Windows Vista and Windows Server 2008.
The clock resolution is 1 millisecond. Its accuracy is usually around 15 ms. It
is possible to improve the accuracy using the `undocumented
NtSetTimerResolution() function
<http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Time/NtSetTimerResolution.html>`_.
There are applications using this undocumented function, example:
`Timer Resolution <http://www.lucashale.com/timer-resolution/>`_.
Windows: timeGetTime
^^^^^^^^^^^^^^^^^^^^
The timeGetTime function retrieves the system time, in milliseconds. The system
time is the time elapsed since Windows was started. Read the `timeGetTime()
documentation
<http://msdn.microsoft.com/en-us/library/windows/desktop/dd757629(v=vs.85).aspx>`_.
The return type of timeGetTime() is a 32-bit unsigned integer. As
GetTickCount(), timeGetTime() rolls over after 2^32 milliseconds (49.7 days).
The default precision of the timeGetTime function can be five milliseconds or
more, depending on the machine.
timeBeginPeriod() can be used to increase the precision of timeGetTime() up to
1 millisecond.
.. note::
timeGetTime() and timeBeginPeriod() are part the Windows multimedia library
and so require to link the program with winmm or to load dynamically the
library.
Solaris: CLOCK_HIGHRES
^^^^^^^^^^^^^^^^^^^^^^
The Solaris OS has an CLOCK_HIGHRES timer that attempts to use an optimal
hardware source, and may give close to nanosecond resolution. CLOCK_HIGHRES is
the nonadjustable, high-resolution clock. For timers created with a clockid_t
value of CLOCK_HIGHRES, the system will attempt to use an optimal hardware
source.
Solaris: gethrtime
^^^^^^^^^^^^^^^^^^
The gethrtime() function returns the current high-resolution real time. Time is
expressed as nanoseconds since some arbitrary time in the past; it is not
correlated in any way to the time of day, and thus is not subject to
resetting or drifting by way of adjtime() or settimeofday(). The hires timer
is ideally suited to performance measurement tasks, where cheap, accurate
interval timing is required.
The linearity of gethrtime() is not preserved accross cpr suspend-resume cycle
(`Bug 4272663 <http://wesunsolve.net/bugid/id/4272663>`_).
Read the `gethrtime() manual page of Solaris 11
<http://docs.oracle.com/cd/E23824_01/html/821-1465/gethrtime-3c.html#scrolltoc>`_.
On Solaris, gethrtime() is the same as clock_gettime(CLOCK_MONOTONIC).
System time
-----------
Windows: GetSystemTimeAsFileTime
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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 1 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_gettime(CLOCK_REALTIME) return the
system clock.
Resolution:
* clock_gettime(): 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().
Windows: 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.
The elapsed time retrieved by the QueryUnbiasedInterruptTime function includes
only time that the system spends in the working state.
QueryUnbiasedInterruptTime() is not monotonic.
QueryUnbiasedInterruptTime() was introduced in Windows 7.
Alternatives: API design
========================
time.highres() function name
----------------------------
Other names were proposed:
* 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.steady(): no OS provides a clock advancing at a steady rate, so
"steady" should be avoided.
* 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.wallclock()
One function with a flag: time.monotonic(strict=False)
------------------------------------------------------
* time.monotonic(strict=False) falls back to the system clock if no monotonic
clock is available or if the monotonic clock failed.
* time.monotonic(strict=True) raises OSError if monotonic clock fails and
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.
One function, no flag
---------------------
time.monotonic() returns (time: float, is_monotonic: bool).
An alternative is to use a function attribute: time.monotonic.is_monotonic. The
attribute value would be None before the first call to time.monotonic().
Working around operating system bugs?
=====================================
Should Python ensure manually that a monotonic clock is truly monotonic by
computing the maximum with the clock value and the previous value?
Since it's relatively straightforward to cache the last value returned using a
static variable, it might be interesting to use this to make sure that the
values returned are indeed monotonic.
* Virtual machines provide less reliable clocks.
* QueryPerformanceCounter() has known bugs (only one is not fixed yet)
Python may only workaround a specific known operating system bug: `KB274323`_
contains a code example to workaround the bug (use GetTickCount() to detect
QueryPerformanceCounter() leap).
Footnotes
=========
.. [#pseudo] "_time" is an hypothetical module only used for the example.
The time module is implemented in C and so there is no need for such module.
Links
=====
Related Python issues:
* `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>`_
* `Issue #14428: Implementation of the PEP 418
<http://bugs.python.org/issue14428>`_
Librairies exposing monotonic clocks:
* `Java: System.nanoTime
<http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#nanoTime()>`_
* `Qt library: QElapsedTimer
<http://qt-project.org/doc/qt-4.8/qelapsedtimer.html>`_
* `glib library: g_get_monotonic_time ()
<http://developer.gnome.org/glib/2.30/glib-Date-and-Time-Functions.html#g-get-monotonic-time>`_
uses GetTickCount64()/GetTickCount() on Windows,
clock_gettime(CLOCK_MONOTONIC) on UNIX or falls back to the system clock
* `python-monotonic-time
<http://code.google.com/p/python-monotonic-time/>`_
(`github <https://github.com/gavinbeatty/python-monotonic-time>`_)
* `monotonic_clock
<https://github.com/ThomasHabets/monotonic_clock>`_
* `Perl: Time::HiRes
<http://perldoc.perl.org/Time/HiRes.html>`_ exposes
clock_gettime(CLOCK_MONOTONIC)
* `Ruby: AbsoluteTime.now
<https://github.com/bwbuchanan/absolute_time/>`_: use
clock_gettime(CLOCK_MONOTONIC), mach_absolute_time() or gettimeofday().
"AbsoluteTime.monotonic?" method indicates if AbsoluteTime.now is monotonic
or not.
Time:
* `C++ Timeout Specification
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3128.html>`_
* `Windows: Game Timing and Multicore Processors
<http://msdn.microsoft.com/en-us/library/ee417693.aspx>`_
* `Implement a Continuously Updating, High-Resolution Time Provider for Windows
<http://msdn.microsoft.com/en-us/magazine/cc163996.aspx>`_
* `clockspeed <http://cr.yp.to/clockspeed.html>`_ uses a hardware tick counter
to compensate for a persistently fast or slow system clock
* `Retrieving system time
<http://en.wikipedia.org/wiki/System_time#Retrieving_system_time>`_
lists hardware clocks and time functions with their resolution
and epoch or range
* On Windows, the JavaScript runtime of Firefox interpolates
GetSystemTimeAsFileTime() with QueryPerformanceCounter() to get
an higher resolution. See the `Bug 363258 - bad millisecond resolution for
(new Date).getTime() / Date.now() on Windows
<https://bugzilla.mozilla.org/show_bug.cgi?id=363258>`_.
* `When microseconds matter
<http://www.ibm.com/developerworks/library/i-seconds/>`_: How the IBM High
Resolution Time Stamp Facility accurately measures itty bits of time