From 517ca4d3fe3ce78a430157daec2072794ed05b80 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 20 Feb 2024 14:34:37 +0100 Subject: [PATCH] Add PyTime API --- docs/api.rst | 30 +++++++++++++++ docs/changelog.rst | 9 +++++ pythoncapi_compat.h | 58 +++++++++++++++++++++++++++++ runtests.py | 3 ++ tests/test_pythoncapi_compat.py | 4 ++ tests/test_pythoncapi_compat_cext.c | 36 ++++++++++++++++++ 6 files changed, 140 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 45a9e18..162b27b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -141,6 +141,36 @@ Python 3.13 See `Py_HashPointer() documentation `__. +.. c:type:: PyTime_t + + A timestamp or duration in nanoseconds, represented as a signed 64-bit + integer. + +.. c:var:: PyTime_t PyTime_MIN + + Minimum value of :c:type:`PyTime_t`. + +.. c:var:: PyTime_t PyTime_MAX + + Maximum value of :c:type:`PyTime_t`. + +.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t) + + See `PyTime_AsSecondsDouble() documentation `__. + +.. c:function:: int PyTime_Monotonic(PyTime_t *result) + + See `PyTime_Monotonic() documentation `__. + +.. c:function:: int PyTime_Time(PyTime_t *result) + + See `PyTime_Time() documentation `__. + +.. c:function:: int PyTime_PerfCounter(PyTime_t *result) + + See `PyTime_PerfCounter() documentation `__. + + Not supported: * ``PySys_Audit()``. diff --git a/docs/changelog.rst b/docs/changelog.rst index 40fdece..76b3d82 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,15 @@ Changelog ========= +* 2024-02-20: Add PyTime API: + + * ``PyTime_t`` type + * ``PyTime_MIN`` and ``PyTime_MAX`` constants + * ``PyTime_AsSecondsDouble()`` + * ``PyTime_Monotonic()`` + * ``PyTime_PerfCounter()`` + * ``PyTime_Time()`` + * 2023-12-15: Add function ``Py_HashPointer()``. * 2023-11-14: Add functions: diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 8b356b2..8cd0472 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1108,6 +1108,64 @@ static inline Py_hash_t Py_HashPointer(const void *ptr) } #endif + +// Python 3.13a4 added a PyTime API. +// Use the private API added to Python 3.5. +#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000 +typedef _PyTime_t PyTime_t; +#define PyTime_MIN _PyTime_MIN +#define PyTime_MAX _PyTime_MAX + +static inline double PyTime_AsSecondsDouble(PyTime_t t) +{ return _PyTime_AsSecondsDouble(t); } + +static inline int PyTime_Monotonic(PyTime_t *result) +{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); } + +static inline int PyTime_Time(PyTime_t *result) +{ return _PyTime_GetSystemClockWithInfo(result, NULL); } + +static inline int PyTime_PerfCounter(PyTime_t *result) +{ +#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION) + return _PyTime_GetPerfCounterWithInfo(result, NULL); +#else + // Cache time.perf_counter() function for best performance + static PyObject *func = NULL; + if (func == NULL) { + // Call time.perf_counter() and convert C double to PyTime_t + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + double d = PyFloat_AsDouble(res); + Py_DECREF(res); + + if (d == -1.0 && PyErr_Occurred()) { + return -1; + } + + // Avoid floor() to avoid having to link to libm + *result = (PyTime_t)(d * 1e9); + return 0; +#endif +} + +#endif + + #ifdef __cplusplus } #endif diff --git a/runtests.py b/runtests.py index fdf1697..dfe0dd3 100755 --- a/runtests.py +++ b/runtests.py @@ -30,6 +30,7 @@ TEST_UPGRADE = os.path.join(TEST_DIR, "test_upgrade_pythoncapi.py") PYTHONS = ( + # CPython "python3-debug", "python3", "python2.7", @@ -43,6 +44,8 @@ "python3.11", "python3.12", "python3.13", + + # PyPy "pypy", "pypy2", "pypy2.7", diff --git a/tests/test_pythoncapi_compat.py b/tests/test_pythoncapi_compat.py index 948906c..177405c 100644 --- a/tests/test_pythoncapi_compat.py +++ b/tests/test_pythoncapi_compat.py @@ -188,6 +188,10 @@ def main(): global VERBOSE VERBOSE = ("-v" in sys.argv[1:] or "--verbose" in sys.argv[1:]) + if (3, 13) <= sys.version_info <= (3, 13, 0, 'alpha', 3): + print("SKIP Python 3.13 alpha 1..3: not supported!") + return + if faulthandler is not None: faulthandler.enable() diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index b8d73a5..94a2cfb 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1526,6 +1526,39 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +#if PY_VERSION_HEX >= 0x03050000 +#define TEST_PYTIME + +static PyObject * +test_time(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyTime_t t; +#define UNINITIALIZED_TIME ((PyTime_t)-483884113929936179) + + t = UNINITIALIZED_TIME; + assert(PyTime_Time(&t) == 0); + assert(t != UNINITIALIZED_TIME); + + t = UNINITIALIZED_TIME; + assert(PyTime_Monotonic(&t) == 0); + assert(t != UNINITIALIZED_TIME); + + // Test multiple times since an implementation uses a cache + for (int i=0; i < 5; i++) { + t = UNINITIALIZED_TIME; + assert(PyTime_PerfCounter(&t) == 0); + assert(t != UNINITIALIZED_TIME); + } + + assert(PyTime_AsSecondsDouble(1) == 1e-9); + assert(PyTime_AsSecondsDouble(1500 * 1000 * 1000) == 1.5); + assert(PyTime_AsSecondsDouble(-500 * 1000 * 1000) == -0.5); + + Py_RETURN_NONE; +} +#endif + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -1559,6 +1592,9 @@ static struct PyMethodDef methods[] = { {"test_unicode", test_unicode, METH_NOARGS, _Py_NULL}, {"test_list", test_list, METH_NOARGS, _Py_NULL}, {"test_hash", test_hash, METH_NOARGS, _Py_NULL}, +#ifdef TEST_PYTIME + {"test_time", test_time, METH_NOARGS, _Py_NULL}, +#endif {_Py_NULL, _Py_NULL, 0, _Py_NULL} };