Fix wording and spelling
This commit is contained in:
parent
4d9406ca17
commit
114408971f
163
pep-0564.rst
163
pep-0564.rst
|
@ -13,15 +13,15 @@ Python-Version: 3.7
|
|||
Abstract
|
||||
========
|
||||
|
||||
Add six new "nanosecond" variant of existing functions to the ``time``
|
||||
Add six new "nanosecond" variants 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.
|
||||
``time_ns()``. While similar to the existing functions without the
|
||||
``_ns`` suffix, they provide nanosecond resolution: they return a number of
|
||||
nanoseconds as a Python ``int``.
|
||||
|
||||
The ``time.time_ns()`` resolution measured in Python is 3 times better
|
||||
than the ``time.time()`` resolution on Linux and Windows.
|
||||
The ``time.time_ns()`` resolution is 3 times better than the ``time.time()``
|
||||
resolution on Linux and Windows.
|
||||
|
||||
|
||||
Rationale
|
||||
|
@ -35,11 +35,11 @@ to nanosecond resolution. More and more clocks have a frequency in MHz,
|
|||
up to GHz for the CPU TSC clock.
|
||||
|
||||
The Python ``time.time()`` function returns the current time as a
|
||||
floatting point number which is usually a 64-bit binary floatting number
|
||||
(in the IEEE 754 format).
|
||||
floating-point number which is usually a 64-bit binary floating-point
|
||||
number (in the IEEE 754 format).
|
||||
|
||||
The problem is that the float type starts to lose nanoseconds after 104
|
||||
days. Conversion from nanoseconds (``int``) to seconds (``float``) and
|
||||
The problem is that the ``float`` type starts to lose nanoseconds after 104
|
||||
days. Converting from nanoseconds (``int``) to seconds (``float``) and
|
||||
then back to nanoseconds (``int``) to check if conversions lose
|
||||
precision::
|
||||
|
||||
|
@ -53,7 +53,8 @@ precision::
|
|||
104 days, 5:59:59.254741
|
||||
|
||||
``time.time()`` returns seconds elapsed since the UNIX epoch: January
|
||||
1st, 1970. This function loses precision since May 1970 (47 years ago)::
|
||||
1st, 1970. This function hasn't had nanosecond precision since May 1970
|
||||
(47 years ago)::
|
||||
|
||||
>>> import datetime
|
||||
>>> unix_epoch = datetime.datetime(1970, 1, 1)
|
||||
|
@ -75,7 +76,7 @@ The PEP was rejected for different reasons:
|
|||
Python.
|
||||
|
||||
* It was not clear if hardware clocks really had a resolution of 1
|
||||
nanosecond, especially at the Python level.
|
||||
nanosecond, or if that made sense at the Python level.
|
||||
|
||||
* The ``decimal.Decimal`` type is uncommon in Python and so requires
|
||||
to adapt code to handle it.
|
||||
|
@ -84,32 +85,32 @@ The PEP was rejected for different reasons:
|
|||
Issues caused by precision loss
|
||||
-------------------------------
|
||||
|
||||
Example 1: measure time delta in long running process
|
||||
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 to detect
|
||||
performance issues at runtime. Such benchmark only lose precision
|
||||
performance issues at runtime. Such benchmark only loses 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
|
||||
less than 100 ns. A difference of a few nanoseconds might become
|
||||
significant.
|
||||
|
||||
Example 2: compare time with different resolution
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Example 2: compare times with different resolution
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Two programs "A" and "B" are runing on the same system and use the system
|
||||
Two programs "A" and "B" are running 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
|
||||
and writes a 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
|
||||
that it reads the clock with second resolution. If that case, there is a
|
||||
that B reads the clock with second resolution. If that case, there is a
|
||||
window of 1 second while the program B can see the timestamp written by A
|
||||
as "in the future".
|
||||
|
||||
Nowadays, more and more databases and filesystems support storing time
|
||||
Nowadays, more and more databases and filesystems support storing times
|
||||
with nanosecond resolution.
|
||||
|
||||
.. note::
|
||||
|
@ -164,15 +165,15 @@ This PEP adds six new functions to the ``time`` module:
|
|||
* ``time.time_ns()``
|
||||
|
||||
These functions are similar to the version without the ``_ns`` suffix,
|
||||
but use nanoseconds as Python ``int``.
|
||||
but return a number of nanoseconds as a 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 large 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.
|
||||
These functions are needed because they may return "large" timestamps,
|
||||
like ``time.time()`` which uses the UNIX epoch as reference, and so their
|
||||
``float``-returning variants are likely to lose precision at the nanosecond
|
||||
resolution.
|
||||
|
||||
Unchanged functions
|
||||
-------------------
|
||||
|
@ -180,13 +181,13 @@ Unchanged functions
|
|||
Since the ``time.clock()`` function was deprecated in Python 3.3, no
|
||||
``time.clock_ns()`` is added.
|
||||
|
||||
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 not lose precision.
|
||||
For example, the maximum value of ``clock_getres()`` should be 1
|
||||
second.
|
||||
Python has other time-returning functions. No nanosecond variant is
|
||||
proposed for these other functions, either because their internal
|
||||
resolution is greater or equal to 1 us, or because their maximum value
|
||||
is small enough to not lose precision. For example, the maximum value of
|
||||
``time.clock_getres()`` should be 1 second.
|
||||
|
||||
Example of unchanged functions:
|
||||
Examples of unchanged functions:
|
||||
|
||||
* ``os`` module: ``sched_rr_get_interval()``, ``times()``, ``wait3()``
|
||||
and ``wait4()``
|
||||
|
@ -200,8 +201,8 @@ Example of unchanged functions:
|
|||
|
||||
See also the `Annex: Clocks Resolution in Python`_.
|
||||
|
||||
A new nanosecond flavor of these functions may be added later if an
|
||||
operating system adds a new function providing better resolution.
|
||||
A new nanosecond-returning flavor of these functions may be added later
|
||||
if an operating system exposes new functions providing better resolution.
|
||||
|
||||
|
||||
Alternatives and discussion
|
||||
|
@ -210,47 +211,48 @@ Alternatives and discussion
|
|||
Sub-nanosecond resolution
|
||||
-------------------------
|
||||
|
||||
``time.time_ns()`` API is not "future-proof": if clocks resolutions
|
||||
increase, new Python functions may be needed.
|
||||
``time.time_ns()`` API is not theoretically future-proof: if clock
|
||||
resolutions continue to increase below the nanosecond level, new Python
|
||||
functions may be needed.
|
||||
|
||||
In practive, the resolution of 1 nanosecond is currently enough for all
|
||||
structures used by all operating systems functions.
|
||||
In practive, the 1 nanosecond resolution is currently enough for all
|
||||
structures returned by all common operating systems functions.
|
||||
|
||||
Hardware clock with a resolution better than 1 nanosecond already
|
||||
exists. For example, the frequency of a CPU TSC clock is the CPU base
|
||||
Hardware clocks with a resolution better than 1 nanosecond already
|
||||
exist. For example, the frequency of a CPU TSC clock is the CPU base
|
||||
frequency: the resolution is around 0.3 ns for a CPU running at 3
|
||||
GHz. Users who have access to such hardware and really need
|
||||
sub-nanosecond resolution can easily extend Python for their needs.
|
||||
Such rare use case don't justify to design the Python standard library
|
||||
sub-nanosecond resolution can however extend Python for their needs.
|
||||
Such a rare use case doesn't justify to design the Python standard library
|
||||
to support sub-nanosecond resolution.
|
||||
|
||||
For the CPython implementation, nanosecond resolution is convenient: the
|
||||
standard and well supported ``int64_t`` type can be used to store time.
|
||||
It supports a time delta between -292 years and 292 years. Using the
|
||||
UNIX epoch as reference, this type supports time since year 1677 to year
|
||||
2262::
|
||||
standard and well supported ``int64_t`` type can be used to store a
|
||||
nanosecond-precise timestamp. It supports a timespan of -292 years
|
||||
to +292 years. Using the UNIX epoch as reference, it therefore supports
|
||||
representing times since year 1677 to year 2262::
|
||||
|
||||
>>> 1970 - 2 ** 63 / (10 ** 9 * 3600 * 24 * 365.25)
|
||||
1677.728976954687
|
||||
>>> 1970 + 2 ** 63 / (10 ** 9 * 3600 * 24 * 365.25)
|
||||
2262.271023045313
|
||||
|
||||
Modify time.time() result type
|
||||
------------------------------
|
||||
Modifying time.time() result type
|
||||
---------------------------------
|
||||
|
||||
It was proposed to modify ``time.time()`` to return a different float
|
||||
It was proposed to modify ``time.time()`` to return a different number
|
||||
type with better precision.
|
||||
|
||||
The PEP 410 proposed to use ``decimal.Decimal`` which already exists and
|
||||
supports arbitray precision, but it was rejected. Apart
|
||||
``decimal.Decimal``, no portable ``float`` type with better precision is
|
||||
currently available in Python.
|
||||
The PEP 410 proposed to return ``decimal.Decimal`` which already exists and
|
||||
supports arbitray precision, but it was rejected. Apart from
|
||||
``decimal.Decimal``, no portable real number type with better precision
|
||||
is currently available in Python.
|
||||
|
||||
Changing the builtin Python ``float`` type is out of the scope of this
|
||||
Changing the built-in Python ``float`` type is out of the scope of this
|
||||
PEP.
|
||||
|
||||
Moreover, changing existing functions to return a new type introduces a
|
||||
risk of breaking the backward compatibility even the new type is
|
||||
risk of breaking the backward compatibility even if the new type is
|
||||
designed carefully.
|
||||
|
||||
|
||||
|
@ -259,7 +261,7 @@ Different types
|
|||
|
||||
Many ideas of new types were proposed to support larger or arbitrary
|
||||
precision: fractions, structures or 2-tuple using integers,
|
||||
fixed-precision floating point number, etc.
|
||||
fixed-point number, etc.
|
||||
|
||||
See also the PEP 410 for a previous long discussion on other types.
|
||||
|
||||
|
@ -267,16 +269,16 @@ Adding a new type requires more effort to support it, than reusing
|
|||
the existing ``int`` type. The standard library, third party code and
|
||||
applications would have to be modified to support it.
|
||||
|
||||
The Python ``int`` type is well known, well supported, ease to
|
||||
manipulate, and supports all arithmetic operations like:
|
||||
The Python ``int`` type is well known, well supported, easy to
|
||||
manipulate, and supports all arithmetic operations such as
|
||||
``dt = t2 - t1``.
|
||||
|
||||
Moreover, using nanoseconds as integer is not new in Python, it's
|
||||
already used for ``os.stat_result`` and
|
||||
Moreover, taking/returning an integer number of nanoseconds is not a
|
||||
new concept in Python, as witnessed by ``os.stat_result`` and
|
||||
``os.utime(ns=(atime_ns, mtime_ns))``.
|
||||
|
||||
.. note::
|
||||
If the Python ``float`` type becomes larger (ex: decimal128 or
|
||||
If the Python ``float`` type becomes larger (e.g. decimal128 or
|
||||
float128), the ``time.time()`` precision will increase as well.
|
||||
|
||||
Different API
|
||||
|
@ -291,13 +293,14 @@ resolution. If each Python module uses a different resolution, it can
|
|||
become difficult to handle different resolutions, instead of just
|
||||
seconds (``time.time()`` returning ``float``) and nanoseconds
|
||||
(``time.time_ns()`` returning ``int``). Moreover, as written above,
|
||||
there is no need for resolution better than 1 nanosecond in practive in
|
||||
there is no need for resolution better than 1 nanosecond in practice in
|
||||
the Python standard library.
|
||||
|
||||
New time_ns module
|
||||
------------------
|
||||
A new module
|
||||
------------
|
||||
|
||||
Add a new ``time_ns`` module which contains the six new functions:
|
||||
It was proposed to add a new ``time_ns`` module containing the following
|
||||
functions:
|
||||
|
||||
* ``time_ns.clock_gettime(clock_id)``
|
||||
* ``time_ns.clock_settime(clock_id, time: int)``
|
||||
|
@ -306,25 +309,25 @@ Add a new ``time_ns`` module which contains the six new functions:
|
|||
* ``time_ns.process_time()``
|
||||
* ``time_ns.time()``
|
||||
|
||||
The first question is if the ``time_ns`` should expose exactly the same
|
||||
API (constants, functions, etc.) than the ``time`` module. It can be
|
||||
painful to maintain two flavors of the ``time`` module. How users use
|
||||
suppose to make a choice between these two modules?
|
||||
The first question is whether the ``time_ns`` module should expose exactly
|
||||
the same API (constants, functions, etc.) as the ``time`` module. It can be
|
||||
painful to maintain two flavors of the ``time`` module. How are users use
|
||||
supposed to make a choice between these two modules?
|
||||
|
||||
If tomorrow, other nanosecond variant are needed in the ``os`` module,
|
||||
If tomorrow, other nanosecond variants are needed in the ``os`` module,
|
||||
will we have to add a new ``os_ns`` module as well? There are functions
|
||||
related to time in many modules: ``time``, ``os``, ``signal``,
|
||||
``resource``, ``select``, etc.
|
||||
|
||||
Another idea is to add a ``time.ns`` submodule or a nested-namespace to
|
||||
get the ``time.ns.time()`` syntax.
|
||||
get the ``time.ns.time()`` syntax, but it suffers from the same issues.
|
||||
|
||||
|
||||
Annex: Clocks Resolution in Python
|
||||
==================================
|
||||
|
||||
This annex contains the resolution of clocks measured in Python, and not
|
||||
the resolution announced by the operating system or the resolution of
|
||||
This annex contains the resolution of clocks as 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
|
||||
|
@ -413,16 +416,16 @@ 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
|
||||
only become larger (worse) as years pass since every day adds
|
||||
86,400,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
|
||||
The difference between ``time.perf_counter()``, ``time.monotonic()``,
|
||||
``time.process_time()`` and their respective nanosecond variants is
|
||||
not visible in this quick script since the script runs for 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 at least 104 days.
|
||||
smaller than 1 week. A significant difference may be seen if uptime
|
||||
reaches 104 days or more.
|
||||
|
||||
``resource.getrusage()`` and ``times()`` have a resolution greater or
|
||||
equal to 1 microsecond, and so don't need a variant with nanosecond
|
||||
|
|
Loading…
Reference in New Issue