[Python-checkins] bpo-31784: Implement PEP 564: add time.time_ns() (#3989)

Victor Stinner webhook-mailer at python.org
Thu Nov 2 10:28:31 EDT 2017


https://github.com/python/cpython/commit/c29b585fd4b5a91d17fc5dd41d86edff28a30da3
commit: c29b585fd4b5a91d17fc5dd41d86edff28a30da3
branch: master
author: Victor Stinner <victor.stinner at gmail.com>
committer: GitHub <noreply at github.com>
date: 2017-11-02T07:28:27-07:00
summary:

bpo-31784: Implement PEP 564: add time.time_ns() (#3989)

Add new time functions:

* time.clock_gettime_ns()
* time.clock_settime_ns()
* time.monotonic_ns()
* time.perf_counter_ns()
* time.process_time_ns()
* time.time_ns()

Add new _PyTime functions:

* _PyTime_FromTimespec()
* _PyTime_FromNanosecondsObject()
* _PyTime_FromTimeval()

Other changes:

* Add also os.times() tests to test_os.
* pytime_fromtimeval() and pytime_fromtimeval() now return
  _PyTime_MAX or _PyTime_MIN on overflow, rather than undefined
  behaviour
* _PyTime_FromNanoseconds() parameter type changes from long long to
  _PyTime_t

files:
A Misc/NEWS.d/next/Library/2017-10-13-23-35-47.bpo-31784.6e57bd.rst
M Doc/library/time.rst
M Doc/whatsnew/3.7.rst
M Include/pytime.h
M Lib/test/test_os.py
M Lib/test/test_time.py
M Modules/_testcapimodule.c
M Modules/timemodule.c
M Python/pytime.c

diff --git a/Doc/library/time.rst b/Doc/library/time.rst
index 253df733bf3..4ffb4d22cd7 100644
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -185,7 +185,7 @@ Functions
    .. versionadded:: 3.3
 
 
-.. function:: clock_gettime(clk_id)
+.. function:: clock_gettime(clk_id) -> float
 
    Return the time of the specified clock *clk_id*.  Refer to
    :ref:`time-clock-id-constants` for a list of accepted values for *clk_id*.
@@ -195,7 +195,16 @@ Functions
    .. versionadded:: 3.3
 
 
-.. function:: clock_settime(clk_id, time)
+.. function:: clock_gettime_ns(clk_id) -> int
+
+   Similar to :func:`clock_gettime` but return time as nanoseconds.
+
+   Availability: Unix.
+
+   .. versionadded:: 3.7
+
+
+.. function:: clock_settime(clk_id, time: float)
 
    Set the time of the specified clock *clk_id*.  Currently,
    :data:`CLOCK_REALTIME` is the only accepted value for *clk_id*.
@@ -205,6 +214,15 @@ Functions
    .. versionadded:: 3.3
 
 
+.. function:: clock_settime_ns(clk_id, time: int)
+
+   Similar to :func:`clock_settime` but set time with nanoseconds.
+
+   Availability: Unix.
+
+   .. versionadded:: 3.7
+
+
 .. function:: ctime([secs])
 
    Convert a time expressed in seconds since the epoch to a string representing
@@ -267,7 +285,7 @@ Functions
    The earliest date for which it can generate a time is platform-dependent.
 
 
-.. function:: monotonic()
+.. function:: monotonic() -> float
 
    Return the value (in fractional seconds) of a monotonic clock, i.e. a clock
    that cannot go backwards.  The clock is not affected by system clock updates.
@@ -287,7 +305,13 @@ Functions
       The function is now always available.
 
 
-.. function:: perf_counter()
+.. function:: monotonic_ns() -> int
+
+   Similar to :func:`monotonic`, but return time as nanoseconds.
+
+   .. versionadded:: 3.7
+
+.. function:: perf_counter() -> float
 
    .. index::
       single: benchmarking
@@ -300,8 +324,14 @@ Functions
 
    .. versionadded:: 3.3
 
+.. function:: perf_counter_ns() -> int
+
+   Similar to :func:`perf_counter`, but return time as nanoseconds.
+
+   .. versionadded:: 3.7
 
-.. function:: process_time()
+
+.. function:: process_time() -> float
 
    .. index::
       single: CPU time
@@ -316,6 +346,12 @@ Functions
 
    .. versionadded:: 3.3
 
+.. function:: process_time_ns() -> int
+
+   Similar to :func:`process_time` but return time as nanoseconds.
+
+   .. versionadded:: 3.7
+
 .. function:: sleep(secs)
 
    Suspend execution of the calling thread for the given number of seconds.
@@ -541,7 +577,7 @@ Functions
    :class:`struct_time`, or having elements of the wrong type, a
    :exc:`TypeError` is raised.
 
-.. function:: time()
+.. function:: time() -> float
 
    Return the time in seconds since the epoch_ as a floating point
    number. The specific date of the epoch and the handling of
@@ -567,6 +603,13 @@ Functions
    of the calendar date may be accessed as attributes.
 
 
+.. function:: time_ns() -> int
+
+   Similar to :func:`time` but returns time as an integer number of nanoseconds
+   since the epoch_.
+
+   .. versionadded:: 3.7
+
 .. function:: tzset()
 
    Reset the time conversion rules used by the library routines. The environment
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index d5836d509be..eb64c6a2b64 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -159,6 +159,32 @@ effort will be made to add such support.
        PEP written by Erik M. Bray; implementation by Masayuki Yamamoto.
 
 
+PEP 564: Add new time functions with nanosecond resolution
+----------------------------------------------------------
+
+Add six new "nanosecond" variants of existing functions to the :mod:`time`
+module:
+
+* :func:`time.clock_gettime_ns`
+* :func:`time.clock_settime_ns`
+* :func:`time.monotonic_ns`
+* :func:`time.perf_counter_ns`
+* :func:`time.process_time_ns`
+* :func:`time.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 is 3 times better than the ``time.time()``
+resolution on Linux and Windows.
+
+.. seealso::
+
+    :pep:`564` -- Add new time functions with nanosecond resolution
+       PEP written and implemented by Victor Stinner
+
+
 Other Language Changes
 ======================
 
@@ -313,6 +339,15 @@ separately.  (Contributed by Barry Warsaw in :issue:`1198569`.)
 time
 ----
 
+The :pep:`564` added six new functions with nanosecond resolution:
+
+* :func:`time.clock_gettime_ns`
+* :func:`time.clock_settime_ns`
+* :func:`time.monotonic_ns`
+* :func:`time.perf_counter_ns`
+* :func:`time.process_time_ns`
+* :func:`time.time_ns`
+
 Add new clock identifiers:
 
 * :data:`time.CLOCK_BOOTTIME` (Linux): Identical to
diff --git a/Include/pytime.h b/Include/pytime.h
index 9f489180fff..4870a9df5b4 100644
--- a/Include/pytime.h
+++ b/Include/pytime.h
@@ -85,7 +85,11 @@ PyAPI_FUNC(_PyTime_t) _PyTime_FromSeconds(int seconds);
             ((_PyTime_t)(seconds) * (1000 * 1000 * 1000))
 
 /* Create a timestamp from a number of nanoseconds. */
-PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(long long ns);
+PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(_PyTime_t ns);
+
+/* Create a timestamp from nanoseconds (Python int). */
+PyAPI_FUNC(int) _PyTime_FromNanosecondsObject(_PyTime_t *t,
+    PyObject *obj);
 
 /* Convert a number of seconds (Python float or int) to a timetamp.
    Raise an exception and return -1 on error, return 0 on success. */
@@ -114,6 +118,10 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t,
    object. */
 PyAPI_FUNC(PyObject *) _PyTime_AsNanosecondsObject(_PyTime_t t);
 
+/* Create a timestamp from a timeval structure.
+   Raise an exception and return -1 on overflow, return 0 on success. */
+PyAPI_FUNC(int) _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv);
+
 /* Convert a timestamp to a timeval structure (microsecond resolution).
    tv_usec is always positive.
    Raise an exception and return -1 if the conversion overflowed,
@@ -140,12 +148,22 @@ PyAPI_FUNC(int) _PyTime_AsTimevalTime_t(
     _PyTime_round_t round);
 
 #if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE)
+/* Create a timestamp from a timespec structure.
+   Raise an exception and return -1 on overflow, return 0 on success. */
+PyAPI_FUNC(int) _PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts);
+
 /* Convert a timestamp to a timespec structure (nanosecond resolution).
    tv_nsec is always positive.
    Raise an exception and return -1 on error, return 0 on success. */
 PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts);
 #endif
 
+/* Compute ticks * mul / div.
+   The caller must ensure that ((div - 1) * mul) cannot overflow. */
+PyAPI_FUNC(_PyTime_t) _PyTime_MulDiv(_PyTime_t ticks,
+    _PyTime_t mul,
+    _PyTime_t div);
+
 /* Get the current time from the system clock.
 
    The function cannot fail. _PyTime_Init() ensures that the system clock
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index eb73af51ac8..4d57bfbf9ab 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -3549,6 +3549,23 @@ def test_bad_pathlike(self):
         self.assertRaises(ZeroDivisionError, self.fspath,
                           _PathLike(ZeroDivisionError()))
 
+
+class TimesTests(unittest.TestCase):
+    def test_times(self):
+        times = os.times()
+        self.assertIsInstance(times, os.times_result)
+
+        for field in ('user', 'system', 'children_user', 'children_system',
+                      'elapsed'):
+            value = getattr(times, field)
+            self.assertIsInstance(value, float)
+
+        if os.name == 'nt':
+            self.assertEqual(times.children_user, 0)
+            self.assertEqual(times.children_system, 0)
+            self.assertEqual(times.elapsed, 0)
+
+
 # Only test if the C version is provided, otherwise TestPEP519 already tested
 # the pure Python implementation.
 if hasattr(os, "_fspath"):
diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py
index a08fd1822b0..b44646da709 100644
--- a/Lib/test/test_time.py
+++ b/Lib/test/test_time.py
@@ -64,6 +64,27 @@ def test_time(self):
         self.assertFalse(info.monotonic)
         self.assertTrue(info.adjustable)
 
+    def test_time_ns_type(self):
+        def check_ns(sec, ns):
+            self.assertIsInstance(ns, int)
+
+            sec_ns = int(sec * 1e9)
+            # tolerate a difference of 50 ms
+            self.assertLess((sec_ns - ns), 50 ** 6, (sec, ns))
+
+        check_ns(time.time(),
+                 time.time_ns())
+        check_ns(time.monotonic(),
+                 time.monotonic_ns())
+        check_ns(time.perf_counter(),
+                 time.perf_counter_ns())
+        check_ns(time.process_time(),
+                 time.process_time_ns())
+
+        if hasattr(time, 'clock_gettime'):
+            check_ns(time.clock_gettime(time.CLOCK_REALTIME),
+                     time.clock_gettime_ns(time.CLOCK_REALTIME))
+
     def test_clock(self):
         with self.assertWarns(DeprecationWarning):
             time.clock()
@@ -76,7 +97,8 @@ def test_clock(self):
     @unittest.skipUnless(hasattr(time, 'clock_gettime'),
                          'need time.clock_gettime()')
     def test_clock_realtime(self):
-        time.clock_gettime(time.CLOCK_REALTIME)
+        t = time.clock_gettime(time.CLOCK_REALTIME)
+        self.assertIsInstance(t, float)
 
     @unittest.skipUnless(hasattr(time, 'clock_gettime'),
                          'need time.clock_gettime()')
diff --git a/Misc/NEWS.d/next/Library/2017-10-13-23-35-47.bpo-31784.6e57bd.rst b/Misc/NEWS.d/next/Library/2017-10-13-23-35-47.bpo-31784.6e57bd.rst
new file mode 100644
index 00000000000..560c82a0ae0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-10-13-23-35-47.bpo-31784.6e57bd.rst
@@ -0,0 +1,5 @@
+Implement the :pep:`564`, add new 6 new functions with nanosecond resolution to
+the :mod:`time` module: :func:`~time.clock_gettime_ns`,
+:func:`~time.clock_settime_ns`, :func:`~time.monotonic_ns`,
+:func:`~time.perf_counter_ns`, :func:`~time.process_time_ns`,
+:func:`~time.time_ns`.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 1f71a09974e..52108091f56 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3946,13 +3946,16 @@ test_pytime_fromsecondsobject(PyObject *self, PyObject *args)
 static PyObject *
 test_pytime_assecondsdouble(PyObject *self, PyObject *args)
 {
-    long long ns;
+    PyObject *obj;
     _PyTime_t ts;
     double d;
 
-    if (!PyArg_ParseTuple(args, "L", &ns))
+    if (!PyArg_ParseTuple(args, "O", &obj)) {
+        return NULL;
+    }
+    if (_PyTime_FromNanosecondsObject(&ts, obj) < 0) {
         return NULL;
-    ts = _PyTime_FromNanoseconds(ns);
+    }
     d = _PyTime_AsSecondsDouble(ts);
     return PyFloat_FromDouble(d);
 }
@@ -3960,23 +3963,28 @@ test_pytime_assecondsdouble(PyObject *self, PyObject *args)
 static PyObject *
 test_PyTime_AsTimeval(PyObject *self, PyObject *args)
 {
-    long long ns;
+    PyObject *obj;
     int round;
     _PyTime_t t;
     struct timeval tv;
     PyObject *seconds;
 
-    if (!PyArg_ParseTuple(args, "Li", &ns, &round))
+    if (!PyArg_ParseTuple(args, "Oi", &obj, &round))
         return NULL;
-    if (check_time_rounding(round) < 0)
+    if (check_time_rounding(round) < 0) {
         return NULL;
-    t = _PyTime_FromNanoseconds(ns);
-    if (_PyTime_AsTimeval(t, &tv, round) < 0)
+    }
+    if (_PyTime_FromNanosecondsObject(&t, obj) < 0) {
         return NULL;
+    }
+    if (_PyTime_AsTimeval(t, &tv, round) < 0) {
+        return NULL;
+    }
 
     seconds = PyLong_FromLongLong(tv.tv_sec);
-    if (seconds == NULL)
+    if (seconds == NULL) {
         return NULL;
+    }
     return Py_BuildValue("Nl", seconds, tv.tv_usec);
 }
 
@@ -3984,15 +3992,19 @@ test_PyTime_AsTimeval(PyObject *self, PyObject *args)
 static PyObject *
 test_PyTime_AsTimespec(PyObject *self, PyObject *args)
 {
-    long long ns;
+    PyObject *obj;
     _PyTime_t t;
     struct timespec ts;
 
-    if (!PyArg_ParseTuple(args, "L", &ns))
+    if (!PyArg_ParseTuple(args, "O", &obj)) {
         return NULL;
-    t = _PyTime_FromNanoseconds(ns);
-    if (_PyTime_AsTimespec(t, &ts) == -1)
+    }
+    if (_PyTime_FromNanosecondsObject(&t, obj) < 0) {
         return NULL;
+    }
+    if (_PyTime_AsTimespec(t, &ts) == -1) {
+        return NULL;
+    }
     return Py_BuildValue("Nl", _PyLong_FromTime_t(ts.tv_sec), ts.tv_nsec);
 }
 #endif
@@ -4000,15 +4012,19 @@ test_PyTime_AsTimespec(PyObject *self, PyObject *args)
 static PyObject *
 test_PyTime_AsMilliseconds(PyObject *self, PyObject *args)
 {
-    long long ns;
+    PyObject *obj;
     int round;
     _PyTime_t t, ms;
 
-    if (!PyArg_ParseTuple(args, "Li", &ns, &round))
+    if (!PyArg_ParseTuple(args, "Oi", &obj, &round)) {
         return NULL;
-    if (check_time_rounding(round) < 0)
+    }
+    if (_PyTime_FromNanosecondsObject(&t, obj) < 0) {
+        return NULL;
+    }
+    if (check_time_rounding(round) < 0) {
         return NULL;
-    t = _PyTime_FromNanoseconds(ns);
+    }
     ms = _PyTime_AsMilliseconds(t, round);
     /* This conversion rely on the fact that _PyTime_t is a number of
        nanoseconds */
@@ -4018,15 +4034,18 @@ test_PyTime_AsMilliseconds(PyObject *self, PyObject *args)
 static PyObject *
 test_PyTime_AsMicroseconds(PyObject *self, PyObject *args)
 {
-    long long ns;
+    PyObject *obj;
     int round;
     _PyTime_t t, ms;
 
-    if (!PyArg_ParseTuple(args, "Li", &ns, &round))
+    if (!PyArg_ParseTuple(args, "Oi", &obj, &round))
         return NULL;
-    if (check_time_rounding(round) < 0)
+    if (_PyTime_FromNanosecondsObject(&t, obj) < 0) {
         return NULL;
-    t = _PyTime_FromNanoseconds(ns);
+    }
+    if (check_time_rounding(round) < 0) {
+        return NULL;
+    }
     ms = _PyTime_AsMicroseconds(t, round);
     /* This conversion rely on the fact that _PyTime_t is a number of
        nanoseconds */
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 347c8282d8e..37abeb95077 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -34,57 +34,90 @@
 #endif /* MS_WINDOWS */
 #endif /* !__WATCOMC__ || __QNX__ */
 
+#define SEC_TO_NS (1000 * 1000 * 1000)
+
 /* Forward declarations */
 static int pysleep(_PyTime_t);
-static PyObject* floattime(_Py_clock_info_t *info);
+
+
+static PyObject*
+_PyFloat_FromPyTime(_PyTime_t t)
+{
+    double d = _PyTime_AsSecondsDouble(t);
+    return PyFloat_FromDouble(d);
+}
+
 
 static PyObject *
 time_time(PyObject *self, PyObject *unused)
 {
-    return floattime(NULL);
+    _PyTime_t t = _PyTime_GetSystemClock();
+    return _PyFloat_FromPyTime(t);
 }
 
+
 PyDoc_STRVAR(time_doc,
 "time() -> floating point number\n\
 \n\
 Return the current time in seconds since the Epoch.\n\
 Fractions of a second may be present if the system clock provides them.");
 
+static PyObject *
+time_time_ns(PyObject *self, PyObject *unused)
+{
+    _PyTime_t t = _PyTime_GetSystemClock();
+    return _PyTime_AsNanosecondsObject(t);
+}
+
+PyDoc_STRVAR(time_ns_doc,
+"time_ns() -> int\n\
+\n\
+Return the current time in nanoseconds since the Epoch.");
+
 #if defined(HAVE_CLOCK)
 
 #ifndef CLOCKS_PER_SEC
-#ifdef CLK_TCK
-#define CLOCKS_PER_SEC CLK_TCK
-#else
-#define CLOCKS_PER_SEC 1000000
-#endif
+#  ifdef CLK_TCK
+#    define CLOCKS_PER_SEC CLK_TCK
+#  else
+#    define CLOCKS_PER_SEC 1000000
+#  endif
 #endif
 
-static PyObject*
-_PyFloat_FromPyTime(_PyTime_t t)
+static int
+_PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
 {
-    double d = _PyTime_AsSecondsDouble(t);
-    return PyFloat_FromDouble(d);
-}
+    static int initialized = 0;
+    clock_t ticks;
 
-static PyObject *
-floatclock(_Py_clock_info_t *info)
-{
-    clock_t value;
-    value = clock();
-    if (value == (clock_t)-1) {
-        PyErr_SetString(PyExc_RuntimeError,
-                "the processor time used is not available "
-                "or its value cannot be represented");
-        return NULL;
+    if (!initialized) {
+        initialized = 1;
+
+        /* must sure that _PyTime_MulDiv(ticks, SEC_TO_NS, CLOCKS_PER_SEC)
+           above cannot overflow */
+        if ((_PyTime_t)CLOCKS_PER_SEC > _PyTime_MAX / SEC_TO_NS) {
+            PyErr_SetString(PyExc_OverflowError,
+                            "CLOCKS_PER_SEC is too large");
+            return -1;
+        }
     }
+
     if (info) {
         info->implementation = "clock()";
         info->resolution = 1.0 / (double)CLOCKS_PER_SEC;
         info->monotonic = 1;
         info->adjustable = 0;
     }
-    return PyFloat_FromDouble((double)value / CLOCKS_PER_SEC);
+
+    ticks = clock();
+    if (ticks == (clock_t)-1) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "the processor time used is not available "
+                        "or its value cannot be represented");
+        return -1;
+    }
+    *tp = _PyTime_MulDiv(ticks, SEC_TO_NS, (_PyTime_t)CLOCKS_PER_SEC);
+    return 0;
 }
 #endif /* HAVE_CLOCK */
 
@@ -95,8 +128,7 @@ perf_counter(_Py_clock_info_t *info)
     if (_PyTime_GetPerfCounterWithInfo(&t, info) < 0) {
         return NULL;
     }
-    double d = _PyTime_AsSecondsDouble(t);
-    return PyFloat_FromDouble(d);
+    return _PyFloat_FromPyTime(t);
 }
 
 #if defined(MS_WINDOWS) || defined(HAVE_CLOCK)
@@ -111,10 +143,15 @@ pyclock(_Py_clock_info_t *info)
                       "instead", 1) < 0) {
         return NULL;
     }
+
 #ifdef MS_WINDOWS
     return perf_counter(info);
 #else
-    return floatclock(info);
+    _PyTime_t t;
+    if (_PyTime_GetClockWithInfo(&t, info) < 0) {
+        return NULL;
+    }
+    return _PyFloat_FromPyTime(t);
 #endif
 }
 
@@ -140,8 +177,9 @@ time_clock_gettime(PyObject *self, PyObject *args)
     int clk_id;
     struct timespec tp;
 
-    if (!PyArg_ParseTuple(args, "i:clock_gettime", &clk_id))
+    if (!PyArg_ParseTuple(args, "i:clock_gettime", &clk_id)) {
         return NULL;
+    }
 
     ret = clock_gettime((clockid_t)clk_id, &tp);
     if (ret != 0) {
@@ -152,9 +190,37 @@ time_clock_gettime(PyObject *self, PyObject *args)
 }
 
 PyDoc_STRVAR(clock_gettime_doc,
-"clock_gettime(clk_id) -> floating point number\n\
+"clock_gettime(clk_id) -> float\n\
 \n\
 Return the time of the specified clock clk_id.");
+
+static PyObject *
+time_clock_gettime_ns(PyObject *self, PyObject *args)
+{
+    int ret;
+    int clk_id;
+    struct timespec ts;
+    _PyTime_t t;
+
+    if (!PyArg_ParseTuple(args, "i:clock_gettime", &clk_id)) {
+        return NULL;
+    }
+
+    ret = clock_gettime((clockid_t)clk_id, &ts);
+    if (ret != 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+    if (_PyTime_FromTimespec(&t, &ts) < 0) {
+        return NULL;
+    }
+    return _PyTime_AsNanosecondsObject(t);
+}
+
+PyDoc_STRVAR(clock_gettime_ns_doc,
+"clock_gettime_ns(clk_id) -> int\n\
+\n\
+Return the time of the specified clock clk_id as nanoseconds.");
 #endif   /* HAVE_CLOCK_GETTIME */
 
 #ifdef HAVE_CLOCK_SETTIME
@@ -188,6 +254,39 @@ PyDoc_STRVAR(clock_settime_doc,
 "clock_settime(clk_id, time)\n\
 \n\
 Set the time of the specified clock clk_id.");
+
+static PyObject *
+time_clock_settime_ns(PyObject *self, PyObject *args)
+{
+    int clk_id;
+    PyObject *obj;
+    _PyTime_t t;
+    struct timespec ts;
+    int ret;
+
+    if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj)) {
+        return NULL;
+    }
+
+    if (_PyTime_FromNanosecondsObject(&t, obj) < 0) {
+        return NULL;
+    }
+    if (_PyTime_AsTimespec(t, &ts) == -1) {
+        return NULL;
+    }
+
+    ret = clock_settime((clockid_t)clk_id, &ts);
+    if (ret != 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(clock_settime_ns_doc,
+"clock_settime_ns(clk_id, time)\n\
+\n\
+Set the time of the specified clock clk_id with nanoseconds.");
 #endif   /* HAVE_CLOCK_SETTIME */
 
 #ifdef HAVE_CLOCK_GETRES
@@ -927,26 +1026,28 @@ should not be relied on.");
 #endif /* HAVE_WORKING_TZSET */
 
 static PyObject *
-pymonotonic(_Py_clock_info_t *info)
+time_monotonic(PyObject *self, PyObject *unused)
 {
-    _PyTime_t t;
-    if (_PyTime_GetMonotonicClockWithInfo(&t, info) < 0) {
-        assert(info != NULL);
-        return NULL;
-    }
+    _PyTime_t t = _PyTime_GetMonotonicClock();
     return _PyFloat_FromPyTime(t);
 }
 
+PyDoc_STRVAR(monotonic_doc,
+"monotonic() -> float\n\
+\n\
+Monotonic clock, cannot go backward.");
+
 static PyObject *
-time_monotonic(PyObject *self, PyObject *unused)
+time_monotonic_ns(PyObject *self, PyObject *unused)
 {
-    return pymonotonic(NULL);
+    _PyTime_t t = _PyTime_GetMonotonicClock();
+    return _PyTime_AsNanosecondsObject(t);
 }
 
-PyDoc_STRVAR(monotonic_doc,
-"monotonic() -> float\n\
+PyDoc_STRVAR(monotonic_ns_doc,
+"monotonic_ns() -> int\n\
 \n\
-Monotonic clock, cannot go backward.");
+Monotonic clock, cannot go backward, as nanoseconds.");
 
 static PyObject *
 time_perf_counter(PyObject *self, PyObject *unused)
@@ -959,47 +1060,61 @@ PyDoc_STRVAR(perf_counter_doc,
 \n\
 Performance counter for benchmarking.");
 
-static PyObject*
-py_process_time(_Py_clock_info_t *info)
+static PyObject *
+time_perf_counter_ns(PyObject *self, PyObject *unused)
+{
+    _PyTime_t t = _PyTime_GetPerfCounter();
+    return _PyTime_AsNanosecondsObject(t);
+}
+
+PyDoc_STRVAR(perf_counter_ns_doc,
+"perf_counter_ns() -> int\n\
+\n\
+Performance counter for benchmarking as nanoseconds.");
+
+static int
+_PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
 {
 #if defined(MS_WINDOWS)
     HANDLE process;
     FILETIME creation_time, exit_time, kernel_time, user_time;
     ULARGE_INTEGER large;
-    double total;
+    _PyTime_t ktime, utime, t;
     BOOL ok;
 
     process = GetCurrentProcess();
-    ok = GetProcessTimes(process, &creation_time, &exit_time, &kernel_time, &user_time);
-    if (!ok)
-        return PyErr_SetFromWindowsErr(0);
+    ok = GetProcessTimes(process, &creation_time, &exit_time,
+                         &kernel_time, &user_time);
+    if (!ok) {
+        PyErr_SetFromWindowsErr(0);
+        return -1;
+    }
 
-    large.u.LowPart = kernel_time.dwLowDateTime;
-    large.u.HighPart = kernel_time.dwHighDateTime;
-    total = (double)large.QuadPart;
-    large.u.LowPart = user_time.dwLowDateTime;
-    large.u.HighPart = user_time.dwHighDateTime;
-    total += (double)large.QuadPart;
     if (info) {
         info->implementation = "GetProcessTimes()";
         info->resolution = 1e-7;
         info->monotonic = 1;
         info->adjustable = 0;
     }
-    return PyFloat_FromDouble(total * 1e-7);
-#else
 
-#if defined(HAVE_SYS_RESOURCE_H)
-    struct rusage ru;
-#endif
-#ifdef HAVE_TIMES
-    struct tms t;
-    static long ticks_per_second = -1;
-#endif
+    large.u.LowPart = kernel_time.dwLowDateTime;
+    large.u.HighPart = kernel_time.dwHighDateTime;
+    ktime = large.QuadPart;
+
+    large.u.LowPart = user_time.dwLowDateTime;
+    large.u.HighPart = user_time.dwHighDateTime;
+    utime = large.QuadPart;
+
+    /* ktime and utime have a resolution of 100 nanoseconds */
+    t = _PyTime_FromNanoseconds((ktime + utime) * 100);
+    *tp = t;
+    return 0;
+#else
 
+    /* clock_gettime */
 #if defined(HAVE_CLOCK_GETTIME) \
     && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF))
-    struct timespec tp;
+    struct timespec ts;
 #ifdef CLOCK_PROF
     const clockid_t clk_id = CLOCK_PROF;
     const char *function = "clock_gettime(CLOCK_PROF)";
@@ -1008,75 +1123,117 @@ py_process_time(_Py_clock_info_t *info)
     const char *function = "clock_gettime(CLOCK_PROCESS_CPUTIME_ID)";
 #endif
 
-    if (clock_gettime(clk_id, &tp) == 0) {
+    if (clock_gettime(clk_id, &ts) == 0) {
         if (info) {
             struct timespec res;
             info->implementation = function;
             info->monotonic = 1;
             info->adjustable = 0;
-            if (clock_getres(clk_id, &res) == 0)
-                info->resolution = res.tv_sec + res.tv_nsec * 1e-9;
-            else
-                info->resolution = 1e-9;
+            if (clock_getres(clk_id, &res)) {
+                PyErr_SetFromErrno(PyExc_OSError);
+                return -1;
+            }
+            info->resolution = res.tv_sec + res.tv_nsec * 1e-9;
         }
-        return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9);
+
+        if (_PyTime_FromTimespec(tp, &ts) < 0) {
+            return -1;
+        }
+        return 0;
     }
 #endif
 
+    /* getrusage(RUSAGE_SELF) */
 #if defined(HAVE_SYS_RESOURCE_H)
+    struct rusage ru;
+
     if (getrusage(RUSAGE_SELF, &ru) == 0) {
-        double total;
-        total = ru.ru_utime.tv_sec + ru.ru_utime.tv_usec * 1e-6;
-        total += ru.ru_stime.tv_sec + ru.ru_stime.tv_usec * 1e-6;
+        _PyTime_t utime, stime;
+
         if (info) {
             info->implementation = "getrusage(RUSAGE_SELF)";
             info->monotonic = 1;
             info->adjustable = 0;
             info->resolution = 1e-6;
         }
-        return PyFloat_FromDouble(total);
+
+        if (_PyTime_FromTimeval(&utime, &ru.ru_utime) < 0) {
+            return -1;
+        }
+        if (_PyTime_FromTimeval(&stime, &ru.ru_stime) < 0) {
+            return -1;
+        }
+
+        _PyTime_t total = utime + utime;
+        *tp = total;
+        return 0;
     }
 #endif
 
+    /* times() */
 #ifdef HAVE_TIMES
+    struct tms t;
+
     if (times(&t) != (clock_t)-1) {
-        double total;
+        static long ticks_per_second = -1;
 
         if (ticks_per_second == -1) {
+            long freq;
 #if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK)
-            ticks_per_second = sysconf(_SC_CLK_TCK);
-            if (ticks_per_second < 1)
-                ticks_per_second = -1;
+            freq = sysconf(_SC_CLK_TCK);
+            if (freq < 1) {
+                freq = -1;
+            }
 #elif defined(HZ)
-            ticks_per_second = HZ;
+            freq = HZ;
 #else
-            ticks_per_second = 60; /* magic fallback value; may be bogus */
+            freq = 60; /* magic fallback value; may be bogus */
 #endif
+
+            if (freq != -1) {
+                /* check that _PyTime_MulDiv(t, SEC_TO_NS, ticks_per_second)
+                   cannot overflow below */
+                if ((_PyTime_t)freq > _PyTime_MAX / SEC_TO_NS) {
+                    PyErr_SetString(PyExc_OverflowError,
+                                    "_SC_CLK_TCK is too large");
+                    return -1;
+                }
+
+                ticks_per_second = freq;
+            }
         }
 
         if (ticks_per_second != -1) {
-            total = (double)t.tms_utime / ticks_per_second;
-            total += (double)t.tms_stime / ticks_per_second;
             if (info) {
                 info->implementation = "times()";
                 info->monotonic = 1;
                 info->adjustable = 0;
-                info->resolution = 1.0 / ticks_per_second;
+                info->resolution = 1.0 / (double)ticks_per_second;
             }
-            return PyFloat_FromDouble(total);
+
+            _PyTime_t total;
+            total = _PyTime_MulDiv(t.tms_utime, SEC_TO_NS, ticks_per_second);
+            total += _PyTime_MulDiv(t.tms_stime, SEC_TO_NS, ticks_per_second);
+            *tp = total;
+            return 0;
         }
     }
 #endif
 
+    /* clock */
     /* Currently, Python 3 requires clock() to build: see issue #22624 */
-    return floatclock(info);
+    return _PyTime_GetClockWithInfo(tp, info);
 #endif
 }
 
 static PyObject *
 time_process_time(PyObject *self, PyObject *unused)
 {
-    return py_process_time(NULL);
+    _PyTime_t t;
+    if (_PyTime_GetProcessTimeWithInfo(&t, NULL) < 0) {
+        return NULL;
+    }
+    return _PyFloat_FromPyTime(t);
 }
 
 PyDoc_STRVAR(process_time_doc,
@@ -1084,6 +1241,22 @@ PyDoc_STRVAR(process_time_doc,
 \n\
 Process time for profiling: sum of the kernel and user-space CPU time.");
 
+static PyObject *
+time_process_time_ns(PyObject *self, PyObject *unused)
+{
+    _PyTime_t t;
+    if (_PyTime_GetProcessTimeWithInfo(&t, NULL) < 0) {
+        return NULL;
+    }
+    return _PyTime_AsNanosecondsObject(t);
+}
+
+PyDoc_STRVAR(process_time_ns_doc,
+"process_time() -> int\n\
+\n\
+Process time for profiling as nanoseconds:\n\
+sum of the kernel and user-space CPU time.");
+
 
 static PyObject *
 time_get_clock_info(PyObject *self, PyObject *args)
@@ -1091,9 +1264,11 @@ time_get_clock_info(PyObject *self, PyObject *args)
     char *name;
     _Py_clock_info_t info;
     PyObject *obj = NULL, *dict, *ns;
+    _PyTime_t t;
 
-    if (!PyArg_ParseTuple(args, "s:get_clock_info", &name))
+    if (!PyArg_ParseTuple(args, "s:get_clock_info", &name)) {
         return NULL;
+    }
 
 #ifdef Py_DEBUG
     info.implementation = NULL;
@@ -1107,61 +1282,84 @@ time_get_clock_info(PyObject *self, PyObject *args)
     info.resolution = 1.0;
 #endif
 
-    if (strcmp(name, "time") == 0)
-        obj = floattime(&info);
+    if (strcmp(name, "time") == 0) {
+        if (_PyTime_GetSystemClockWithInfo(&t, &info) < 0) {
+            return NULL;
+        }
+    }
 #ifdef PYCLOCK
-    else if (strcmp(name, "clock") == 0)
+    else if (strcmp(name, "clock") == 0) {
         obj = pyclock(&info);
+        if (obj == NULL) {
+            return NULL;
+        }
+        Py_DECREF(obj);
+    }
 #endif
-    else if (strcmp(name, "monotonic") == 0)
-        obj = pymonotonic(&info);
-    else if (strcmp(name, "perf_counter") == 0)
-        obj = perf_counter(&info);
-    else if (strcmp(name, "process_time") == 0)
-        obj = py_process_time(&info);
+    else if (strcmp(name, "monotonic") == 0) {
+        if (_PyTime_GetMonotonicClockWithInfo(&t, &info) < 0) {
+            return NULL;
+        }
+    }
+    else if (strcmp(name, "perf_counter") == 0) {
+        if (_PyTime_GetPerfCounterWithInfo(&t, &info) < 0) {
+            return NULL;
+        }
+    }
+    else if (strcmp(name, "process_time") == 0) {
+        if (_PyTime_GetProcessTimeWithInfo(&t, &info) < 0) {
+            return NULL;
+        }
+    }
     else {
         PyErr_SetString(PyExc_ValueError, "unknown clock");
         return NULL;
     }
-    if (obj == NULL)
-        return NULL;
-    Py_DECREF(obj);
 
     dict = PyDict_New();
-    if (dict == NULL)
+    if (dict == NULL) {
         return NULL;
+    }
 
     assert(info.implementation != NULL);
     obj = PyUnicode_FromString(info.implementation);
-    if (obj == NULL)
+    if (obj == NULL) {
         goto error;
-    if (PyDict_SetItemString(dict, "implementation", obj) == -1)
+    }
+    if (PyDict_SetItemString(dict, "implementation", obj) == -1) {
         goto error;
+    }
     Py_CLEAR(obj);
 
     assert(info.monotonic != -1);
     obj = PyBool_FromLong(info.monotonic);
-    if (obj == NULL)
+    if (obj == NULL) {
         goto error;
-    if (PyDict_SetItemString(dict, "monotonic", obj) == -1)
+    }
+    if (PyDict_SetItemString(dict, "monotonic", obj) == -1) {
         goto error;
+    }
     Py_CLEAR(obj);
 
     assert(info.adjustable != -1);
     obj = PyBool_FromLong(info.adjustable);
-    if (obj == NULL)
+    if (obj == NULL) {
         goto error;
-    if (PyDict_SetItemString(dict, "adjustable", obj) == -1)
+    }
+    if (PyDict_SetItemString(dict, "adjustable", obj) == -1) {
         goto error;
+    }
     Py_CLEAR(obj);
 
     assert(info.resolution > 0.0);
     assert(info.resolution <= 1.0);
     obj = PyFloat_FromDouble(info.resolution);
-    if (obj == NULL)
+    if (obj == NULL) {
         goto error;
-    if (PyDict_SetItemString(dict, "resolution", obj) == -1)
+    }
+    if (PyDict_SetItemString(dict, "resolution", obj) == -1) {
         goto error;
+    }
     Py_CLEAR(obj);
 
     ns = _PyNamespace_New(dict);
@@ -1284,14 +1482,17 @@ PyInit_timezone(PyObject *m) {
 
 static PyMethodDef time_methods[] = {
     {"time",            time_time, METH_NOARGS, time_doc},
+    {"time_ns",         time_time_ns, METH_NOARGS, time_ns_doc},
 #ifdef PYCLOCK
     {"clock",           time_clock, METH_NOARGS, clock_doc},
 #endif
 #ifdef HAVE_CLOCK_GETTIME
     {"clock_gettime",   time_clock_gettime, METH_VARARGS, clock_gettime_doc},
+    {"clock_gettime_ns",time_clock_gettime_ns, METH_VARARGS, clock_gettime_ns_doc},
 #endif
 #ifdef HAVE_CLOCK_SETTIME
     {"clock_settime",   time_clock_settime, METH_VARARGS, clock_settime_doc},
+    {"clock_settime_ns",time_clock_settime_ns, METH_VARARGS, clock_settime_ns_doc},
 #endif
 #ifdef HAVE_CLOCK_GETRES
     {"clock_getres",    time_clock_getres, METH_VARARGS, clock_getres_doc},
@@ -1315,8 +1516,11 @@ static PyMethodDef time_methods[] = {
     {"tzset",           time_tzset, METH_NOARGS, tzset_doc},
 #endif
     {"monotonic",       time_monotonic, METH_NOARGS, monotonic_doc},
+    {"monotonic_ns",    time_monotonic_ns, METH_NOARGS, monotonic_ns_doc},
     {"process_time",    time_process_time, METH_NOARGS, process_time_doc},
+    {"process_time_ns", time_process_time_ns, METH_NOARGS, process_time_ns_doc},
     {"perf_counter",    time_perf_counter, METH_NOARGS, perf_counter_doc},
+    {"perf_counter_ns", time_perf_counter_ns, METH_NOARGS, perf_counter_ns_doc},
     {"get_clock_info",  time_get_clock_info, METH_VARARGS, get_clock_info_doc},
     {NULL,              NULL}           /* sentinel */
 };
@@ -1411,18 +1615,6 @@ PyInit_time(void)
     return m;
 }
 
-static PyObject*
-floattime(_Py_clock_info_t *info)
-{
-    _PyTime_t t;
-    if (_PyTime_GetSystemClockWithInfo(&t, info) < 0) {
-        assert(info != NULL);
-        return NULL;
-    }
-    return _PyFloat_FromPyTime(t);
-}
-
-
 /* Implement pysleep() for various platforms.
    When interrupted (or when another error occurs), return -1 and
    set an exception; else return 0. */
diff --git a/Python/pytime.c b/Python/pytime.c
index f19bb3673e4..5a98d1d3297 100644
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -43,8 +43,7 @@ _PyTime_overflow(void)
 }
 
 
-#if defined(MS_WINDOWS) || defined(__APPLE__)
-Py_LOCAL_INLINE(_PyTime_t)
+_PyTime_t
 _PyTime_MulDiv(_PyTime_t ticks, _PyTime_t mul, _PyTime_t div)
 {
     _PyTime_t intpart, remaining;
@@ -60,7 +59,6 @@ _PyTime_MulDiv(_PyTime_t ticks, _PyTime_t mul, _PyTime_t div)
     remaining /= div;
     return intpart * mul + remaining;
 }
-#endif   /* defined(MS_WINDOWS) || defined(__APPLE__) */
 
 
 time_t
@@ -254,19 +252,44 @@ _PyTime_FromSeconds(int seconds)
 }
 
 _PyTime_t
-_PyTime_FromNanoseconds(long long ns)
+_PyTime_FromNanoseconds(_PyTime_t ns)
+{
+    /* _PyTime_t already uses nanosecond resolution, no conversion needed */
+    return ns;
+}
+
+int
+_PyTime_FromNanosecondsObject(_PyTime_t *tp, PyObject *obj)
 {
+    long long nsec;
     _PyTime_t t;
-    Py_BUILD_ASSERT(sizeof(long long) <= sizeof(_PyTime_t));
-    t = Py_SAFE_DOWNCAST(ns, long long, _PyTime_t);
-    return t;
+
+    if (!PyLong_Check(obj)) {
+        PyErr_Format(PyExc_TypeError, "expect int, got %s",
+                     Py_TYPE(obj)->tp_name);
+        return -1;
+    }
+
+    Py_BUILD_ASSERT(sizeof(long long) == sizeof(_PyTime_t));
+    nsec = PyLong_AsLongLong(obj);
+    if (nsec == -1 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
+            _PyTime_overflow();
+        }
+        return -1;
+    }
+
+    /* _PyTime_t already uses nanosecond resolution, no conversion needed */
+    t = (_PyTime_t)nsec;
+    *tp = t;
+    return 0;
 }
 
 #ifdef HAVE_CLOCK_GETTIME
 static int
-_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts, int raise)
+pytime_fromtimespec(_PyTime_t *tp, struct timespec *ts, int raise)
 {
-    _PyTime_t t;
+    _PyTime_t t, nsec;
     int res = 0;
 
     Py_BUILD_ASSERT(sizeof(ts->tv_sec) <= sizeof(_PyTime_t));
@@ -277,19 +300,42 @@ _PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts, int raise)
             _PyTime_overflow();
         }
         res = -1;
+        t = (t > 0) ? _PyTime_MAX : _PyTime_MIN;
+    }
+    else {
+        t = t * SEC_TO_NS;
     }
-    t = t * SEC_TO_NS;
 
-    t += ts->tv_nsec;
+    nsec = ts->tv_nsec;
+    /* The following test is written for positive only nsec */
+    assert(nsec >= 0);
+    if (t > _PyTime_MAX - nsec) {
+        if (raise) {
+            _PyTime_overflow();
+        }
+        res = -1;
+        t = _PyTime_MAX;
+    }
+    else {
+        t += nsec;
+    }
 
     *tp = t;
     return res;
 }
-#elif !defined(MS_WINDOWS)
+
+int
+_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts)
+{
+    return pytime_fromtimespec(tp, ts, 1);
+}
+#endif
+
+#if !defined(MS_WINDOWS)
 static int
-_PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv, int raise)
+pytime_fromtimeval(_PyTime_t *tp, struct timeval *tv, int raise)
 {
-    _PyTime_t t;
+    _PyTime_t t, usec;
     int res = 0;
 
     Py_BUILD_ASSERT(sizeof(tv->tv_sec) <= sizeof(_PyTime_t));
@@ -300,14 +346,35 @@ _PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv, int raise)
             _PyTime_overflow();
         }
         res = -1;
+        t = (t > 0) ? _PyTime_MAX : _PyTime_MIN;
+    }
+    else {
+        t = t * SEC_TO_NS;
     }
-    t = t * SEC_TO_NS;
 
-    t += (_PyTime_t)tv->tv_usec * US_TO_NS;
+    usec = (_PyTime_t)tv->tv_usec * US_TO_NS;
+    /* The following test is written for positive only usec */
+    assert(usec >= 0);
+    if (t > _PyTime_MAX - usec) {
+        if (raise) {
+            _PyTime_overflow();
+        }
+        res = -1;
+        t = _PyTime_MAX;
+    }
+    else {
+        t += usec;
+    }
 
     *tp = t;
     return res;
 }
+
+int
+_PyTime_FromTimeval(_PyTime_t *tp, struct timeval *tv)
+{
+    return pytime_fromtimeval(tp, tv, 1);
+}
 #endif
 
 static int
@@ -632,7 +699,7 @@ pygettimeofday(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
         }
         return -1;
     }
-    if (_PyTime_FromTimespec(tp, &ts, raise) < 0) {
+    if (pytime_fromtimespec(tp, &ts, raise) < 0) {
         return -1;
     }
 
@@ -662,7 +729,7 @@ pygettimeofday(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
         }
         return -1;
     }
-    if (_PyTime_FromTimeval(tp, &tv, raise) < 0) {
+    if (pytime_fromtimeval(tp, &tv, raise) < 0) {
         return -1;
     }
 
@@ -841,7 +908,7 @@ pymonotonic(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
         }
         info->resolution = res.tv_sec + res.tv_nsec * 1e-9;
     }
-    if (_PyTime_FromTimespec(tp, &ts, raise) < 0) {
+    if (pytime_fromtimespec(tp, &ts, raise) < 0) {
         return -1;
     }
 #endif



More information about the Python-checkins mailing list