Skip to content

Commit

Permalink
Add Py3.13 support in cpython/time.pxd (GH-6187)
Browse files Browse the repository at this point in the history
* Use new PyTime_*Raw() functions in cpython/time.pxd for Py3.13.

* Allow PyPy (and others) to define "PyTime_t" etc. and use them if available, otherwise fall back to the older functions separately.
  • Loading branch information
scoder committed May 13, 2024
1 parent 563f24f commit 562fbbf
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 13 deletions.
87 changes: 78 additions & 9 deletions Cython/Includes/cpython/time.pxd
Original file line number Diff line number Diff line change
@@ -1,23 +1,73 @@
"""
Cython implementation of (parts of) the standard library time module.
Note: On implementations that lack a C-API for monotonic/perfcounter clocks
(like PyPy), the fallback code uses the system clock which may return absolute
time values from a different value range, differing from those provided by
Python's "time" module.
"""

from libc.stdint cimport int64_t
from cpython.exc cimport PyErr_SetFromErrno

cdef extern from *:
"""
#if PY_VERSION_HEX >= 0x030d00A4
#define __Pyx_PyTime_TimeUnchecked() _PyTime_TimeUnchecked()
#if PY_VERSION_HEX >= 0x030d00b1 || defined(PyTime_t)
#define __Pyx_PyTime_t PyTime_t
#else
#define __Pyx_PyTime_t _PyTime_t
#endif
#if PY_VERSION_HEX >= 0x030d00b1 || defined(PyTime_TimeRaw)
static CYTHON_INLINE __Pyx_PyTime_t __Pyx_PyTime_TimeRaw(void) {
__Pyx_PyTime_t tic;
(void) PyTime_TimeRaw(&tic);
return tic;
}
#else
#define __Pyx_PyTime_TimeRaw() _PyTime_GetSystemClock()
#endif
#if PY_VERSION_HEX >= 0x030d00b1 || defined(PyTime_MonotonicRaw)
static CYTHON_INLINE __Pyx_PyTime_t __Pyx_PyTime_MonotonicRaw(void) {
__Pyx_PyTime_t tic;
(void) PyTime_MonotonicRaw(&tic);
return tic;
}
#elif CYTHON_COMPILING_IN_PYPY && !defined(_PyTime_GetMonotonicClock)
#define __Pyx_PyTime_MonotonicRaw() _PyTime_GetSystemClock()
#else
#define __Pyx_PyTime_MonotonicRaw() _PyTime_GetMonotonicClock()
#endif
#if PY_VERSION_HEX >= 0x030d00b1 || defined(PyTime_PerfCounterRaw)
static CYTHON_INLINE __Pyx_PyTime_t __Pyx_PyTime_PerfCounterRaw(void) {
__Pyx_PyTime_t tic;
(void) PyTime_PerfCounterRaw(&tic);
return tic;
}
#elif CYTHON_COMPILING_IN_PYPY && !defined(_PyTime_GetPerfCounter)
#define __Pyx_PyTime_PerfCounterRaw() __Pyx_PyTime_MonotonicRaw()
#else
#define __Pyx_PyTime_PerfCounterRaw() _PyTime_GetPerfCounter()
#endif
#if PY_VERSION_HEX >= 0x030d00b1 || defined(PyTime_AsSecondsDouble)
#define __Pyx_PyTime_AsSecondsDouble(t) PyTime_AsSecondsDouble(t)
#else
#define __Pyx_PyTime_TimeUnchecked() _PyTime_GetSystemClock()
#define __Pyx_PyTime_AsSecondsDouble(t) _PyTime_AsSecondsDouble(t)
#endif
"""
ctypedef int64_t _PyTime_t
_PyTime_t PyTime_TimeUnchecked "__Pyx_PyTime_TimeUnchecked" () nogil
double PyTime_AsSecondsDouble "__Pyx_PyTime_AsSecondsDouble" (_PyTime_t t) nogil
ctypedef int64_t PyTime_t "__Pyx_PyTime_t"

ctypedef PyTime_t _PyTime_t "__Pyx_PyTime_t" # legacy, use "PyTime_t" instead
PyTime_t PyTime_TimeUnchecked "__Pyx_PyTime_TimeRaw" () noexcept nogil # legacy, use "PyTime_TimeRaw" instead

PyTime_t PyTime_TimeRaw "__Pyx_PyTime_TimeRaw" () noexcept nogil
PyTime_t PyTime_MonotonicRaw "__Pyx_PyTime_MonotonicRaw" () noexcept nogil
PyTime_t PyTime_PerfCounterRaw "__Pyx_PyTime_PerfCounterRaw" () noexcept nogil
double PyTime_AsSecondsDouble "__Pyx_PyTime_AsSecondsDouble" (PyTime_t t) noexcept nogil


from libc.time cimport (
tm,
Expand All @@ -27,13 +77,32 @@ from libc.time cimport (


cdef inline double time() noexcept nogil:
cdef:
_PyTime_t tic
cdef PyTime_t tic = PyTime_TimeRaw()
return PyTime_AsSecondsDouble(tic)


cdef inline int64_t time_ns() noexcept nogil:
return <int64_t> PyTime_TimeRaw()


tic = PyTime_TimeUnchecked()
cdef inline double perf_counter() noexcept nogil:
cdef PyTime_t tic = PyTime_PerfCounterRaw()
return PyTime_AsSecondsDouble(tic)


cdef inline int64_t perf_counter_ns() noexcept nogil:
return <int64_t> PyTime_PerfCounterRaw()


cdef inline double monotonic() noexcept nogil:
cdef PyTime_t tic = PyTime_MonotonicRaw()
return PyTime_AsSecondsDouble(tic)


cdef inline int64_t monotonic_ns() noexcept nogil:
return <int64_t> PyTime_MonotonicRaw()


cdef inline int _raise_from_errno() except -1 with gil:
PyErr_SetFromErrno(RuntimeError)
return <int> -1 # Let the C compiler know that this function always raises.
Expand Down
101 changes: 97 additions & 4 deletions tests/run/time_pxd.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,118 @@ import time

from cpython cimport time as ctime

import sys
cdef bint IS_PYPY = hasattr(sys, 'pypy_version_info')


def test_time():
"""
>>> tic1, tic2, tic3 = test_time()
>>> tic1 <= tic3 # sanity check
>>> tic1 <= tic3 or (tic1, tic3) # sanity check
True
>>> tic1 <= tic2
>>> tic1 <= tic2 or (tic1, tic2)
True
>>> tic2 <= tic3
>>> tic2 <= tic3 or (tic2, tic3)
True
"""
# check that ctime.time() matches time.time() to within call-time tolerance
# check that C-API matches Py-API to within call-time tolerance
tic1 = time.time()
tic2 = ctime.time()
tic3 = time.time()

return tic1, tic2, tic3


def test_time_ns():
"""
>>> tic1, tic2, tic3 = test_time_ns()
>>> tic1 <= tic3 or (tic1, tic3) # sanity check
True
>>> tic1 <= tic2 or (tic1, tic2)
True
>>> tic2 <= tic3 or (tic2, tic3)
True
"""
# check that C-API matches Py-API to within call-time tolerance
tic1 = time.time_ns()
tic2 = ctime.time_ns()
tic3 = time.time_ns()

return tic1, tic2, tic3


def test_perf_counter():
"""
>>> tic1, tic2, tic3 = test_perf_counter()
>>> tic1 <= tic3 or (tic1, tic3) # sanity check
True
>>> tic1 <= tic2 or (tic1, tic2)
True
>>> tic2 <= tic3 or (tic2, tic3)
True
"""
# check that C-API matches Py-API to within call-time tolerance
tic1 = time.perf_counter() if not IS_PYPY else ctime.perf_counter()
tic2 = ctime.perf_counter()
tic3 = time.perf_counter() if not IS_PYPY else ctime.perf_counter()

return tic1, tic2, tic3


def test_perf_counter_ns():
"""
>>> tic1, tic2, tic3 = test_perf_counter_ns()
>>> tic1 <= tic3 or (tic1, tic3) # sanity check
True
>>> tic1 <= tic2 or (tic1, tic2)
True
>>> tic2 <= tic3 or (tic2, tic3)
True
"""
# check that C-API matches Py-API to within call-time tolerance
tic1 = time.perf_counter_ns() if not IS_PYPY else ctime.perf_counter_ns()
tic2 = ctime.perf_counter_ns()
tic3 = time.perf_counter_ns() if not IS_PYPY else ctime.perf_counter_ns()

return tic1, tic2, tic3


def test_monotonic():
"""
>>> tic1, tic2, tic3 = test_monotonic()
>>> tic1 <= tic3 or (tic1, tic3) # sanity check
True
>>> tic1 <= tic2 or (tic1, tic2)
True
>>> tic2 <= tic3 or (tic2, tic3)
True
"""
# check that C-API matches Py-API to within call-time tolerance
tic1 = time.monotonic() if not IS_PYPY else ctime.monotonic()
tic2 = ctime.monotonic()
tic3 = time.monotonic() if not IS_PYPY else ctime.monotonic()

return tic1, tic2, tic3


def test_monotonic_ns():
"""
>>> tic1, tic2, tic3 = test_monotonic_ns()
>>> tic1 <= tic3 or (tic1, tic3) # sanity check
True
>>> tic1 <= tic2 or (tic1, tic2)
True
>>> tic2 <= tic3 or (tic2, tic3)
True
"""
# check that C-API matches Py-API to within call-time tolerance
tic1 = time.monotonic_ns() if not IS_PYPY else ctime.monotonic_ns()
tic2 = ctime.monotonic_ns()
tic3 = time.monotonic_ns() if not IS_PYPY else ctime.monotonic_ns()

return tic1, tic2, tic3


def test_localtime():
"""
>>> ltp, ltc = test_localtime()
Expand Down

0 comments on commit 562fbbf

Please sign in to comment.