[Python-checkins] cpython: Issue #22117: Add a new Python timestamp format _PyTime_t to pytime.h

victor.stinner python-checkins at python.org
Fri Mar 27 14:02:17 CET 2015


https://hg.python.org/cpython/rev/2309597e7a00
changeset:   95213:2309597e7a00
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Fri Mar 27 13:31:18 2015 +0100
summary:
  Issue #22117: Add a new Python timestamp format _PyTime_t to pytime.h

In practice, _PyTime_t is a number of nanoseconds. Its C type is a 64-bit
signed number. It's integer value is in the range [-2^63; 2^63-1]. In seconds,
the range is around [-292 years; +292 years]. In term of Epoch timestamp
(1970-01-01), it can store a date between 1677-09-21 and 2262-04-11.

The API has a resolution of 1 nanosecond and use integer number. With a
resolution on 1 nanosecond, 64-bit IEEE 754 floating point numbers loose
precision after 194 days. It's not the case with this API. The drawback is
overflow for values outside [-2^63; 2^63-1], but these values are unlikely for
most Python modules, except of the datetime module.

New functions:

- _PyTime_GetMonotonicClock()
- _PyTime_FromObject()
- _PyTime_AsMilliseconds()
- _PyTime_AsTimeval()

This change uses these new functions in time.sleep() to avoid rounding issues.

The new API will be extended step by step, and the old API will be removed step
by step. Currently, some code is duplicated just to be able to move
incrementally, instead of pushing a large change at once.

files:
  Include/pytime.h     |   34 +++
  Modules/timemodule.c |   38 +--
  Python/pytime.c      |  311 ++++++++++++++++++++++++++++++-
  3 files changed, 361 insertions(+), 22 deletions(-)


diff --git a/Include/pytime.h b/Include/pytime.h
--- a/Include/pytime.h
+++ b/Include/pytime.h
@@ -111,6 +111,40 @@
    Return 0 on success, raise an exception and return -1 on error. */
 PyAPI_FUNC(int) _PyTime_Init(void);
 
+/****************** NEW _PyTime_t API **********************/
+
+#ifdef PY_INT64_T
+typedef PY_INT64_T _PyTime_t;
+#else
+#  error "_PyTime_t need signed 64-bit integer type"
+#endif
+
+/* Convert a Python float or int to a timetamp.
+   Raise an exception and return -1 on error, return 0 on success. */
+PyAPI_FUNC(int) _PyTime_FromObject(_PyTime_t *t,
+    PyObject *obj,
+    _PyTime_round_t round);
+
+/* Convert timestamp to a number of milliseconds (10^-3 seconds). */
+PyAPI_FUNC(_PyTime_t)
+_PyTime_AsMilliseconds(_PyTime_t t,
+    _PyTime_round_t round);
+
+/* Convert a timestamp to a timeval structure. */
+PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
+    struct timeval *tv,
+    _PyTime_round_t round);
+
+/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards.
+   The clock is not affected by system clock updates. The reference point of
+   the returned value is undefined, so that only the difference between the
+   results of consecutive calls is valid.
+
+   The function cannot fail. _PyTime_Init() ensures that a monotonic clock
+   is available and works. */
+PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void);
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -31,7 +31,7 @@
 #endif /* !__WATCOMC__ || __QNX__ */
 
 /* Forward declarations */
-static int floatsleep(double);
+static int pysleep(_PyTime_t);
 static PyObject* floattime(_Py_clock_info_t *info);
 
 static PyObject *
@@ -218,17 +218,17 @@
 #endif   /* HAVE_CLOCK_GETTIME */
 
 static PyObject *
-time_sleep(PyObject *self, PyObject *args)
+time_sleep(PyObject *self, PyObject *obj)
 {
-    double secs;
-    if (!PyArg_ParseTuple(args, "d:sleep", &secs))
+    _PyTime_t secs;
+    if (_PyTime_FromObject(&secs, obj, _PyTime_ROUND_UP))
         return NULL;
     if (secs < 0) {
         PyErr_SetString(PyExc_ValueError,
                         "sleep length must be non-negative");
         return NULL;
     }
-    if (floatsleep(secs) != 0)
+    if (pysleep(secs) != 0)
         return NULL;
     Py_INCREF(Py_None);
     return Py_None;
@@ -1258,7 +1258,7 @@
     {"clock_settime",   time_clock_settime, METH_VARARGS, clock_settime_doc},
     {"clock_getres",    time_clock_getres, METH_VARARGS, clock_getres_doc},
 #endif
-    {"sleep",           time_sleep, METH_VARARGS, sleep_doc},
+    {"sleep",           time_sleep, METH_O, sleep_doc},
     {"gmtime",          time_gmtime, METH_VARARGS, gmtime_doc},
     {"localtime",       time_localtime, METH_VARARGS, localtime_doc},
     {"asctime",         time_asctime, METH_VARARGS, asctime_doc},
@@ -1379,34 +1379,30 @@
 }
 
 
-/* Implement floatsleep() for various platforms.
+/* Implement pysleep() for various platforms.
    When interrupted (or when another error occurs), return -1 and
    set an exception; else return 0. */
 
 static int
-floatsleep(double secs)
+pysleep(_PyTime_t secs)
 {
-    _PyTime_timeval deadline, monotonic;
+    _PyTime_t deadline, monotonic;
 #ifndef MS_WINDOWS
     struct timeval timeout;
-    double frac;
     int err = 0;
 #else
-    double millisecs;
+    _PyTime_t millisecs;
     unsigned long ul_millis;
     DWORD rc;
     HANDLE hInterruptEvent;
 #endif
 
-    _PyTime_monotonic(&deadline);
-    _PyTime_AddDouble(&deadline, secs, _PyTime_ROUND_UP);
+    deadline = _PyTime_GetMonotonicClock() + secs;
 
     do {
 #ifndef MS_WINDOWS
-        frac = fmod(secs, 1.0);
-        secs = floor(secs);
-        timeout.tv_sec = (long)secs;
-        timeout.tv_usec = (long)(frac*1e6);
+        if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_UP) < 0)
+            return -1;
 
         Py_BEGIN_ALLOW_THREADS
         err = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
@@ -1420,7 +1416,7 @@
             return -1;
         }
 #else
-        millisecs = secs * 1000.0;
+        millisecs = _PyTime_AsMilliseconds(secs, _PyTime_ROUND_UP);
         if (millisecs > (double)ULONG_MAX) {
             PyErr_SetString(PyExc_OverflowError,
                             "sleep length is too large");
@@ -1453,9 +1449,9 @@
         if (PyErr_CheckSignals())
             return -1;
 
-        _PyTime_monotonic(&monotonic);
-        secs = _PyTime_INTERVAL(monotonic, deadline);
-        if (secs <= 0.0)
+        monotonic = _PyTime_GetMonotonicClock();
+        secs = deadline - monotonic;
+        if (secs <= 00)
             break;
         /* retry with the recomputed delay */
     } while (1);
diff --git a/Python/pytime.c b/Python/pytime.c
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -7,11 +7,21 @@
 #include <mach/mach_time.h>   /* mach_absolute_time(), mach_timebase_info() */
 #endif
 
+/* To millisecond (10^-3) */
 #define SEC_TO_MS 1000
+
+/* To microseconds (10^-6) */
 #define MS_TO_US 1000
+#define SEC_TO_US (SEC_TO_MS * MS_TO_US)
+
+/* To nanoseconds (10^-9) */
 #define US_TO_NS 1000
+#define MS_TO_NS (MS_TO_US * US_TO_NS)
+#define SEC_TO_NS (SEC_TO_MS * MS_TO_NS)
 
-#define SEC_TO_US (SEC_TO_MS * MS_TO_US)
+#ifdef MS_WINDOWS
+static OSVERSIONINFOEX winver;
+#endif
 
 static int
 pygettimeofday(_PyTime_timeval *tp, _Py_clock_info_t *info, int raise)
@@ -390,10 +400,305 @@
     tv->tv_usec %= SEC_TO_US;
 }
 
+/****************** NEW _PyTime_t API **********************/
+
+static void
+_PyTime_overflow(void)
+{
+    PyErr_SetString(PyExc_OverflowError,
+                    "timestamp too large to convert to C _PyTime_t");
+}
+
+#if !defined(MS_WINDOWS) && !defined(__APPLE__)
+static int
+_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts)
+{
+    _PyTime_t t;
+    t = (_PyTime_t)ts->tv_sec * SEC_TO_NS;
+    if (t / SEC_TO_NS != ts->tv_sec) {
+        _PyTime_overflow();
+        return -1;
+    }
+
+    t += ts->tv_nsec;
+
+    *tp = t;
+    return 0;
+}
+#endif
+
+int
+_PyTime_FromObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round)
+{
+    if (PyFloat_Check(obj)) {
+        double d, err;
+
+        /* convert to a number of nanoseconds */
+        d = PyFloat_AsDouble(obj);
+        d *= 1e9;
+
+        /* FIXME: use sign */
+        if (round == _PyTime_ROUND_UP)
+            d = ceil(d);
+        else
+            d = floor(d);
+
+        *t = (_PyTime_t)d;
+        err = d - (double)*t;
+        if (fabs(err) >= 1.0) {
+            _PyTime_overflow();
+            return -1;
+        }
+        return 0;
+    }
+    else {
+#ifdef HAVE_LONG_LONG
+        PY_LONG_LONG sec;
+        sec = PyLong_AsLongLong(obj);
+        assert(sizeof(PY_LONG_LONG) <= sizeof(_PyTime_t));
+#else
+        long sec;
+        sec = PyLong_AsLong(obj);
+        assert(sizeof(PY_LONG_LONG) <= sizeof(_PyTime_t));
+#endif
+        if (sec == -1 && PyErr_Occurred()) {
+            if (PyErr_ExceptionMatches(PyExc_OverflowError))
+                _PyTime_overflow();
+            return -1;
+        }
+        *t = sec * SEC_TO_NS;
+        if (*t / SEC_TO_NS != sec) {
+            _PyTime_overflow();
+            return -1;
+        }
+        return 0;
+    }
+}
+
+static _PyTime_t
+_PyTime_Multiply(_PyTime_t t, unsigned int multiply, _PyTime_round_t round)
+{
+    _PyTime_t k;
+    if (multiply < SEC_TO_NS) {
+        k = SEC_TO_NS / multiply;
+        if (round == _PyTime_ROUND_UP)
+            return (t + k - 1) / k;
+        else
+            return t / k;
+    }
+    else {
+        k = multiply / SEC_TO_NS;
+        return t * k;
+    }
+}
+
+_PyTime_t
+_PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round)
+{
+    return _PyTime_Multiply(t, 1000, round);
+}
+
+int
+_PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round)
+{
+    _PyTime_t secs, ns;
+
+    secs = t / SEC_TO_NS;
+    ns = t % SEC_TO_NS;
+
+#ifdef MS_WINDOWS
+    /* On Windows, timeval.tv_sec is a long (32 bit),
+       whereas time_t can be 64-bit. */
+    assert(sizeof(tv->tv_sec) == sizeof(long));
+#if SIZEOF_TIME_T > SIZEOF_LONG
+    if (secs > LONG_MAX) {
+        _PyTime_overflow();
+        return -1;
+    }
+#endif
+    tv->tv_sec = (long)secs;
+#else
+    /* On OpenBSD 5.4, timeval.tv_sec is a long.
+       Example: long is 64-bit, whereas time_t is 32-bit. */
+    tv->tv_sec = secs;
+    if ((_PyTime_t)tv->tv_sec != secs) {
+        _PyTime_overflow();
+        return -1;
+    }
+#endif
+
+    if (round == _PyTime_ROUND_UP)
+        tv->tv_usec = (int)((ns + US_TO_NS - 1) / US_TO_NS);
+    else
+        tv->tv_usec = (int)(ns / US_TO_NS);
+    return 0;
+}
+
+static int
+pymonotonic_new(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
+{
+#ifdef Py_DEBUG
+    static int last_set = 0;
+    static _PyTime_t last = 0;
+#endif
+#if defined(MS_WINDOWS)
+    static ULONGLONG (*GetTickCount64) (void) = NULL;
+    static ULONGLONG (CALLBACK *Py_GetTickCount64)(void);
+    static int has_gettickcount64 = -1;
+    ULONGLONG result;
+
+    assert(info == NULL || raise);
+
+    if (has_gettickcount64 == -1) {
+        /* GetTickCount64() was added to Windows Vista */
+        has_gettickcount64 = (winver.dwMajorVersion >= 6);
+        if (has_gettickcount64) {
+            HINSTANCE hKernel32;
+            hKernel32 = GetModuleHandleW(L"KERNEL32");
+            *(FARPROC*)&Py_GetTickCount64 = GetProcAddress(hKernel32,
+                                                           "GetTickCount64");
+            assert(Py_GetTickCount64 != NULL);
+        }
+    }
+
+    if (has_gettickcount64) {
+        result = Py_GetTickCount64();
+    }
+    else {
+        static DWORD last_ticks = 0;
+        static DWORD n_overflow = 0;
+        DWORD ticks;
+
+        ticks = GetTickCount();
+        if (ticks < last_ticks)
+            n_overflow++;
+        last_ticks = ticks;
+
+        result = (ULONGLONG)n_overflow << 32;
+        result += ticks;
+    }
+
+    *tp = result * MS_TO_NS;
+    if (*tp / MS_TO_NS != result) {
+        if (raise) {
+            _PyTime_overflow();
+            return -1;
+        }
+        /* Hello, time traveler! */
+        assert(0);
+    }
+
+    if (info) {
+        DWORD timeAdjustment, timeIncrement;
+        BOOL isTimeAdjustmentDisabled, ok;
+        if (has_gettickcount64)
+            info->implementation = "GetTickCount64()";
+        else
+            info->implementation = "GetTickCount()";
+        info->monotonic = 1;
+        ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement,
+                                     &isTimeAdjustmentDisabled);
+        if (!ok) {
+            PyErr_SetFromWindowsErr(0);
+            return -1;
+        }
+        info->resolution = timeIncrement * 1e-7;
+        info->adjustable = 0;
+    }
+
+#elif defined(__APPLE__)
+    static mach_timebase_info_data_t timebase;
+    uint64_t time;
+
+    if (timebase.denom == 0) {
+        /* According to the Technical Q&A QA1398, mach_timebase_info() cannot
+           fail: https://developer.apple.com/library/mac/#qa/qa1398/ */
+        (void)mach_timebase_info(&timebase);
+    }
+
+    time = mach_absolute_time();
+
+    /* apply timebase factor */
+    time *= timebase.numer;
+    time /= timebase.denom;
+
+    *tp = time;
+
+    if (info) {
+        info->implementation = "mach_absolute_time()";
+        info->resolution = (double)timebase.numer / timebase.denom * 1e-9;
+        info->monotonic = 1;
+        info->adjustable = 0;
+    }
+
+#else
+    struct timespec ts;
+#ifdef CLOCK_HIGHRES
+    const clockid_t clk_id = CLOCK_HIGHRES;
+    const char *implementation = "clock_gettime(CLOCK_HIGHRES)";
+#else
+    const clockid_t clk_id = CLOCK_MONOTONIC;
+    const char *implementation = "clock_gettime(CLOCK_MONOTONIC)";
+#endif
+
+    assert(info == NULL || raise);
+
+    if (clock_gettime(clk_id, &ts) != 0) {
+        if (raise) {
+            PyErr_SetFromErrno(PyExc_OSError);
+            return -1;
+        }
+        return -1;
+    }
+
+    if (info) {
+        struct timespec res;
+        info->monotonic = 1;
+        info->implementation = implementation;
+        info->adjustable = 0;
+        if (clock_getres(clk_id, &res) != 0) {
+            PyErr_SetFromErrno(PyExc_OSError);
+            return -1;
+        }
+        info->resolution = res.tv_sec + res.tv_nsec * 1e-9;
+    }
+    if (_PyTime_FromTimespec(tp, &ts) < 0)
+        return -1;
+#endif
+#ifdef Py_DEBUG
+    /* monotonic clock cannot go backward */
+    assert(!last_set || last <= *tp);
+    last = *tp;
+    last_set = 1;
+#endif
+    return 0;
+}
+
+_PyTime_t
+_PyTime_GetMonotonicClock(void)
+{
+    _PyTime_t t;
+    if (pymonotonic_new(&t, NULL, 0) < 0) {
+        /* cannot happen, _PyTime_Init() checks that pymonotonic_new() works */
+        assert(0);
+        t = 0;
+    }
+    return t;
+}
+
 int
 _PyTime_Init(void)
 {
     _PyTime_timeval tv;
+    _PyTime_t t;
+
+#ifdef MS_WINDOWS
+    winver.dwOSVersionInfoSize = sizeof(winver);
+    if (!GetVersionEx((OSVERSIONINFO*)&winver)) {
+        PyErr_SetFromWindowsErr(0);
+        return -1;
+    }
+#endif
 
     /* ensure that the system clock works */
     if (_PyTime_gettimeofday_info(&tv, NULL) < 0)
@@ -402,5 +707,9 @@
     /* ensure that the operating system provides a monotonic clock */
     if (_PyTime_monotonic_info(&tv, NULL) < 0)
         return -1;
+
+    /* ensure that the operating system provides a monotonic clock */
+    if (pymonotonic_new(&t, NULL, 1) < 0)
+        return -1;
     return 0;
 }

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list