PEP 564: add process_time()
Explain why other functions don't need a nanosecond variant.
This commit is contained in:
parent
9d8fd95001
commit
fcbf6126d3
164
pep-0564.rst
164
pep-0564.rst
|
@ -13,14 +13,15 @@ Python-Version: 3.7
|
|||
Abstract
|
||||
========
|
||||
|
||||
Add five new functions to the ``time`` module: ``time_ns()``,
|
||||
``perf_counter_ns()``, ``monotonic_ns()``, ``clock_gettime_ns()`` and
|
||||
``clock_settime_ns()``. They are similar to the function without the
|
||||
``_ns`` suffix, but have nanosecond resolution: use a number of
|
||||
nanoseconds as a Python int.
|
||||
Add six new "nanosecond" variant of existing functions to the ``time``
|
||||
module: ``clock_gettime_ns()``, ``clock_settime_ns()``,
|
||||
``monotonic_ns()``, ``perf_counter_ns()``, ``process_time_ns()`` and
|
||||
``time_ns()``. Similar to the existing functions without the ``_ns``
|
||||
suffix, they have nanosecond resolution: use a number of nanoseconds as
|
||||
a Python int.
|
||||
|
||||
The best ``time.time_ns()`` resolution measured in Python is 3 times
|
||||
better then ``time.time()`` resolution on Linux and Windows.
|
||||
The ``time.time_ns()`` resolution measured in Python is 3 times better
|
||||
than the ``time.time()`` resolution on Linux and Windows.
|
||||
|
||||
|
||||
Rationale
|
||||
|
@ -83,13 +84,14 @@ The PEP was rejected for different reasons:
|
|||
Issues caused by precision loss
|
||||
-------------------------------
|
||||
|
||||
Example 1: measure time delta
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Example 1: measure time delta in long running process
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A server is running for longer than 104 days. A clock is read before
|
||||
and after running a function to measure its performance. This benchmark
|
||||
lose precision only because the float type used by clocks, not because
|
||||
of the clock resolution.
|
||||
A server is running for longer than 104 days. A clock is read before and
|
||||
after running a function to measure its performance to detect
|
||||
performance issues at runtime. Such benchmark only lose precision
|
||||
because of the float type used by clocks, not because of the clock
|
||||
resolution.
|
||||
|
||||
On Python microbenchmarks, it is common to see function calls taking
|
||||
less than 100 ns. A difference of a single nanosecond becomes
|
||||
|
@ -98,8 +100,8 @@ significant.
|
|||
Example 2: compare time with different resolution
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Two programs "A" and "B" are runing on the same system, so use the system
|
||||
block. The program A reads the system clock with nanosecond resolution
|
||||
Two programs "A" and "B" are runing on the same system and use the system
|
||||
clock. The program A reads the system clock with nanosecond resolution
|
||||
and writes the timestamp with nanosecond resolution. The program B reads
|
||||
the timestamp with nanosecond resolution, but compares it to the system
|
||||
clock read with a worse resolution. To simplify the example, let's say
|
||||
|
@ -152,12 +154,13 @@ Changes
|
|||
New functions
|
||||
-------------
|
||||
|
||||
This PEP adds five new functions to the ``time`` module:
|
||||
This PEP adds six new functions to the ``time`` module:
|
||||
|
||||
* ``time.clock_gettime_ns(clock_id)``
|
||||
* ``time.clock_settime_ns(clock_id, time: int)``
|
||||
* ``time.perf_counter_ns()``
|
||||
* ``time.monotonic_ns()``
|
||||
* ``time.perf_counter_ns()``
|
||||
* ``time.process_time_ns()``
|
||||
* ``time.time_ns()``
|
||||
|
||||
These functions are similar to the version without the ``_ns`` suffix,
|
||||
|
@ -166,16 +169,22 @@ but use nanoseconds as Python ``int``.
|
|||
For example, ``time.monotonic_ns() == int(time.monotonic() * 1e9)`` if
|
||||
``monotonic()`` value is small enough to not lose precision.
|
||||
|
||||
These functions are needed because they handle big timestamps, like
|
||||
time.time() which uses the UNIX epoch as reference, and so their version
|
||||
without the ``_ns`` suffix are likely to lose precision at the
|
||||
nanosecond resolution.
|
||||
|
||||
Unchanged functions
|
||||
-------------------
|
||||
|
||||
This PEP only proposed to add new functions getting or setting clocks
|
||||
with nanosecond resolution. Clocks are likely to lose precision,
|
||||
especially when their reference is the UNIX epoch.
|
||||
Since the ``time.clock()`` function was deprecated in Python 3.3, no
|
||||
``time.clock_ns()`` is added.
|
||||
|
||||
Python has other functions handling time (get time, timeout, etc.), but
|
||||
no nanosecond variant is proposed for them since they are less likely to
|
||||
lose precision.
|
||||
Python has other functions handling time. No nanosecond variant was
|
||||
proposed because their internal resolution is greater or equal to 1 us,
|
||||
or because their maximum value is a small enough to lose precision. For
|
||||
example, the maximum value of ``clock_getres()`` is likely to be 1
|
||||
second.
|
||||
|
||||
Example of unchanged functions:
|
||||
|
||||
|
@ -189,11 +198,10 @@ Example of unchanged functions:
|
|||
|
||||
* ``time`` module: ``clock_getres()``
|
||||
|
||||
Since the ``time.clock()`` function was deprecated in Python 3.3, no
|
||||
``time.clock_ns()`` is added.
|
||||
See also the `Annex: Clocks Resolution in Python`_.
|
||||
|
||||
New nanosecond flavor of these functions may be added later, if a
|
||||
concrete use case comes in.
|
||||
New nanosecond flavor of these functions may be added later if an
|
||||
operating system adds a new function provided better resolution.
|
||||
|
||||
|
||||
Alternatives and discussion
|
||||
|
@ -289,12 +297,13 @@ the Python standard library.
|
|||
New time_ns module
|
||||
------------------
|
||||
|
||||
Add a new ``time_ns`` module which contains the five new functions:
|
||||
Add a new ``time_ns`` module which contains the six new functions:
|
||||
|
||||
* ``time_ns.clock_gettime(clock_id)``
|
||||
* ``time_ns.clock_settime(clock_id, time: int)``
|
||||
* ``time_ns.perf_counter()``
|
||||
* ``time_ns.monotonic()``
|
||||
* ``time_ns.perf_counter()``
|
||||
* ``time_ns.process_time()``
|
||||
* ``time_ns.time()``
|
||||
|
||||
The first question is if the ``time_ns`` should expose exactly the same
|
||||
|
@ -314,8 +323,15 @@ get the ``time.ns.time()`` syntax.
|
|||
Annex: Clocks Resolution in Python
|
||||
==================================
|
||||
|
||||
Script ot measure the smallest difference between two ``time.time()`` and
|
||||
``time.time_ns()`` reads ignoring differences of zero::
|
||||
This annex contains the resolution of clocks measured in Python, and not
|
||||
the resolution announced by the operating system or the resolution of
|
||||
the internal structure used by the operating system.
|
||||
|
||||
Script
|
||||
------
|
||||
|
||||
Example of script ot measure the smallest difference between two
|
||||
``time.time()`` and ``time.time_ns()`` reads ignoring differences of zero::
|
||||
|
||||
import math
|
||||
import time
|
||||
|
@ -335,36 +351,82 @@ Script ot measure the smallest difference between two ``time.time()`` and
|
|||
min_dt = min(filter(bool, min_dt))
|
||||
print("min time() delta: %s ns" % math.ceil(min_dt * 1e9))
|
||||
|
||||
Results of time(), perf_counter() and monotonic().
|
||||
Linux
|
||||
-----
|
||||
|
||||
Linux (kernel 4.12 on Fedora 26):
|
||||
|
||||
* time_ns(): **84 ns**
|
||||
* time(): **239 ns**
|
||||
* perf_counter_ns(): 84 ns
|
||||
* perf_counter(): 82 ns
|
||||
* monotonic_ns(): 84 ns
|
||||
* monotonic(): 81 ns
|
||||
==================== ==========
|
||||
Function Resolution
|
||||
==================== ==========
|
||||
clock() 1 us
|
||||
monotonic() 81 ns
|
||||
monotonic_ns() 84 ns
|
||||
perf_counter() 82 ns
|
||||
perf_counter_ns() 84 ns
|
||||
process_time() 2 ns
|
||||
process_time_ns() 1 ns
|
||||
resource.getrusage() 1 us
|
||||
time() **239 ns**
|
||||
time_ns() **84 ns**
|
||||
times().elapsed 10 ms
|
||||
times().user 10 ms
|
||||
==================== ==========
|
||||
|
||||
Notes on resolutions:
|
||||
|
||||
* ``clock()`` frequency is ``CLOCKS_PER_SECOND`` which is 1,000,000 Hz
|
||||
(1 MHz): resolution of 1 us.
|
||||
* ``times()`` frequency is ``os.sysconf("SC_CLK_TCK")`` (or the ``HZ``
|
||||
constant) which is equal to 100 Hz: resolution of 10 ms.
|
||||
* ``resource.getrusage()``, ``os.wait3()`` and ``os.wait4()`` use the
|
||||
``ru_usage`` structure. The type of the ``ru_usage.ru_utime`` and
|
||||
``ru_usage.ru_stime`` fields is the ``timeval`` structure which has a
|
||||
resolution of 1 us.
|
||||
|
||||
Windows
|
||||
-------
|
||||
|
||||
Windows 8.1:
|
||||
|
||||
* time_ns(): **318000 ns**
|
||||
* time(): **894070 ns**
|
||||
* perf_counter_ns(): 100 ns
|
||||
* perf_counter(): 100 ns
|
||||
* monotonic_ns(): 15000000 ns
|
||||
* monotonic(): 15000000 ns
|
||||
================= =============
|
||||
Function Resolution
|
||||
================= =============
|
||||
monotonic() 15 ms
|
||||
monotonic_ns() 15 ms
|
||||
perf_counter() 100 ns
|
||||
perf_counter_ns() 100 ns
|
||||
process_time() 15.6 ms
|
||||
process_time_ns() 15.6 ms
|
||||
time() **894.1 us**
|
||||
time_ns() **318 us**
|
||||
================= =============
|
||||
|
||||
The difference on ``time.time()`` is significant: **84 ns (2.8x better)
|
||||
vs 239 ns on Linux and 318 us (2.8x better) vs 894 us on Windows**. The
|
||||
difference (presion loss) will be larger next years since every day adds
|
||||
864,00,000,000,000 nanoseconds to the system clock.
|
||||
The frequency of ``perf_counter()`` and ``perf_counter_ns()`` comes from
|
||||
``QueryPerformanceFrequency()``. The frequency is usually 10 MHz: resolution of
|
||||
100 ns. In old Windows versions, the frequency was sometimes 3,579,545 Hz (3.6
|
||||
MHz): resolution of 279 ns.
|
||||
|
||||
The difference on ``time.perf_counter()`` and ``time.monotonic clock()``
|
||||
is not visible in this quick script since the script runs less than 1
|
||||
Analysis
|
||||
--------
|
||||
|
||||
The resolution of ``time.time_ns()`` is much better than
|
||||
``time.time()``: **84 ns (2.8x better) vs 239 ns on Linux and 318 us
|
||||
(2.8x better) vs 894 us on Windows**. The ``time.time()`` resolution will
|
||||
becomes larger (worse) next years since every day adds
|
||||
864,00,000,000,000 nanoseconds to the system clock which increases the
|
||||
precision loss.
|
||||
|
||||
The difference between ``time.perf_counter()``, ``time.monotonic
|
||||
clock()``, ``time.process_time()`` and their nanosecond variant is
|
||||
not visible in this quick script since the script runs less than 1
|
||||
minute, and the uptime of the computer used to run the script was
|
||||
smaller than 1 week. A significant difference should be seen with an
|
||||
uptime of 104 days or greater.
|
||||
uptime of at least 104 days.
|
||||
|
||||
``resource.getrusage()`` and ``times()`` have a resolution greater or
|
||||
equal to 1 microsecond, and so don't need a variant with nanosecond
|
||||
resolution.
|
||||
|
||||
.. note::
|
||||
Internally, Python starts ``monotonic()`` and ``perf_counter()``
|
||||
|
|
Loading…
Reference in New Issue