From c29b585fd4b5a91d17fc5dd41d86edff28a30da3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 2 Nov 2017 07:28:27 -0700 Subject: 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 --- Doc/library/time.rst | 55 ++- Doc/whatsnew/3.7.rst | 35 ++ Include/pytime.h | 20 +- Lib/test/test_os.py | 17 + Lib/test/test_time.py | 24 +- .../2017-10-13-23-35-47.bpo-31784.6e57bd.rst | 5 + Modules/_testcapimodule.c | 61 ++- Modules/timemodule.c | 426 +++++++++++++++------ Python/pytime.c | 105 ++++- 9 files changed, 583 insertions(+), 165 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-10-13-23-35-47.bpo-31784.6e57bd.rst diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 253df73..4ffb4d2 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 d5836d5..eb64c6a 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 9f48918..4870a9d 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 eb73af5..4d57bfb 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3549,6 +3549,23 @@ class TestPEP519(unittest.TestCase): 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 a08fd18..b44646d 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -64,6 +64,27 @@ class TimeTestCase(unittest.TestCase): 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 @@ class TimeTestCase(unittest.TestCase): @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 0000000..560c82a --- /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 1f71a09..5210809 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 347c828..37abeb9 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 f19bb36..5a98d1d 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 -- cgit v0.12