Skip to content

Commit

Permalink
pythongh-110850: Add PyTime_t C API (pythonGH-115215)
Browse files Browse the repository at this point in the history
* pythongh-110850: Add PyTime_t C API

Add PyTime_t API:

* PyTime_t type.
* PyTime_MIN and PyTime_MAX constants.
* PyTime_AsSecondsDouble(), PyTime_Monotonic(),
  PyTime_PerfCounter() and PyTime_GetSystemClock() functions.

Co-authored-by: Victor Stinner <vstinner@python.org>
  • Loading branch information
encukou and vstinner committed Feb 12, 2024
1 parent c39272e commit 879f454
Show file tree
Hide file tree
Showing 19 changed files with 448 additions and 114 deletions.
83 changes: 83 additions & 0 deletions Doc/c-api/time.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.. highlight:: c

PyTime C API
============

.. versionadded:: 3.13

The clock C API provides access to system clocks.
It is similar to the Python :mod:`time` module.

For C API related to the :mod:`datetime` module, see :ref:`datetimeobjects`.


Types
-----

.. c:type:: PyTime_t
A timestamp or duration in nanoseconds, represented as a signed 64-bit
integer.

The reference point for timestamps depends on the clock used. For example,
:c:func:`PyTime_Time` returns timestamps relative to the UNIX epoch.

The supported range is around [-292.3 years; +292.3 years].
Using the Unix epoch (January 1st, 1970) as reference, the supported date
range is around [1677-09-21; 2262-04-11].
The exact limits are exposed as constants:

.. 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`.


Clock Functions
---------------

The following functions take a pointer to a :c:expr:`PyTime_t` that they
set to the value of a particular clock.
Details of each clock are given in the documentation of the corresponding
Python function.

The functions return ``0`` on success, or ``-1`` (with an exception set)
on failure.

On integer overflow, they set the :c:data:`PyExc_OverflowError` exception and
set ``*result`` to the value clamped to the ``[PyTime_MIN; PyTime_MAX]``
range.
(On current systems, integer overflows are likely caused by misconfigured
system time.)

As any other C API (unless otherwise specified), the functions must be called
with the :term:`GIL` held.

.. c:function:: int PyTime_Monotonic(PyTime_t *result)
Read the monotonic clock.
See :func:`time.monotonic` for important details on this clock.
.. c:function:: int PyTime_PerfCounter(PyTime_t *result)
Read the performance counter.
See :func:`time.perf_counter` for important details on this clock.
.. c:function:: int PyTime_Time(PyTime_t *result)
Read the “wall clock” time.
See :func:`time.time` for details important on this clock.
Conversion functions
--------------------
.. c:function:: double PyTime_AsSecondsDouble(PyTime_t t)
Convert a timestamp to a number of seconds as a C :c:expr:`double`.
The function cannot fail, but note that :c:expr:`double` has limited
accuracy for large values.
1 change: 1 addition & 0 deletions Doc/c-api/utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ and parsing function arguments and constructing Python values from C values.
hash.rst
reflection.rst
codec.rst
time.rst
perfmaps.rst
13 changes: 8 additions & 5 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,14 @@
('c:type', 'wchar_t'),
('c:type', '__int64'),
('c:type', 'unsigned __int64'),
('c:type', 'double'),
# Standard C structures
('c:struct', 'in6_addr'),
('c:struct', 'in_addr'),
('c:struct', 'stat'),
('c:struct', 'statvfs'),
('c:struct', 'timeval'),
('c:struct', 'timespec'),
# Standard C macros
('c:macro', 'LLONG_MAX'),
('c:macro', 'LLONG_MIN'),
Expand Down Expand Up @@ -269,12 +272,12 @@
('py:meth', 'index'), # list.index, tuple.index, etc.
]

# gh-106948: Copy standard C types declared in the "c:type" domain to the
# "c:identifier" domain, since "c:function" markup looks for types in the
# "c:identifier" domain. Use list() to not iterate on items which are being
# added
# gh-106948: Copy standard C types declared in the "c:type" domain and C
# structures declared in the "c:struct" domain to the "c:identifier" domain,
# since "c:function" markup looks for types in the "c:identifier" domain. Use
# list() to not iterate on items which are being added
for role, name in list(nitpick_ignore):
if role == 'c:type':
if role in ('c:type', 'c:struct'):
nitpick_ignore.append(('c:identifier', name))
del role, name

Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,16 @@ New Features
* Add :c:func:`Py_HashPointer` function to hash a pointer.
(Contributed by Victor Stinner in :gh:`111545`.)

* Add PyTime C API:

* :c:type:`PyTime_t` type.
* :c:var:`PyTime_MIN` and :c:var:`PyTime_MAX` constants.
* :c:func:`PyTime_AsSecondsDouble`
:c:func:`PyTime_Monotonic`, :c:func:`PyTime_PerfCounter`, and
:c:func:`PyTime_Time` functions.

(Contributed by Victor Stinner and Petr Viktorin in :gh:`110850`.)


Porting to Python 3.13
----------------------
Expand Down
1 change: 1 addition & 0 deletions Include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
#include "weakrefobject.h"
#include "structseq.h"
#include "cpython/picklebufobject.h"
#include "cpython/pytime.h"
#include "codecs.h"
#include "pyerrors.h"
#include "pythread.h"
Expand Down
23 changes: 23 additions & 0 deletions Include/cpython/pytime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// PyTime_t C API: see Doc/c-api/time.rst for the documentation.

#ifndef Py_LIMITED_API
#ifndef Py_PYTIME_H
#define Py_PYTIME_H
#ifdef __cplusplus
extern "C" {
#endif

typedef int64_t PyTime_t;
#define PyTime_MIN INT64_MIN
#define PyTime_MAX INT64_MAX

PyAPI_FUNC(double) PyTime_AsSecondsDouble(PyTime_t t);
PyAPI_FUNC(int) PyTime_Monotonic(PyTime_t *result);
PyAPI_FUNC(int) PyTime_PerfCounter(PyTime_t *result);
PyAPI_FUNC(int) PyTime_Time(PyTime_t *result);

#ifdef __cplusplus
}
#endif
#endif /* Py_PYTIME_H */
#endif /* Py_LIMITED_API */
99 changes: 51 additions & 48 deletions Include/internal/pycore_time.h
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
// The _PyTime_t API is written to use timestamp and timeout values stored in
// various formats and to read clocks.
// Internal PyTime_t C API: see Doc/c-api/time.rst for the documentation.
//
// The _PyTime_t type is an integer to support directly common arithmetic
// operations like t1 + t2.
// The PyTime_t type is an integer to support directly common arithmetic
// operations such as t1 + t2.
//
// The _PyTime_t API supports a resolution of 1 nanosecond. The _PyTime_t type
// is signed to support negative timestamps. The supported range is around
// [-292.3 years; +292.3 years]. Using the Unix epoch (January 1st, 1970), the
// supported date range is around [1677-09-21; 2262-04-11].
// Time formats:
//
// Formats:
// * Seconds.
// * Seconds as a floating point number (C double).
// * Milliseconds (10^-3 seconds).
// * Microseconds (10^-6 seconds).
// * 100 nanoseconds (10^-7 seconds), used on Windows.
// * Nanoseconds (10^-9 seconds).
// * timeval structure, 1 microsecond (10^-6 seconds).
// * timespec structure, 1 nanosecond (10^-9 seconds).
//
// * seconds
// * seconds as a floating pointer number (C double)
// * milliseconds (10^-3 seconds)
// * microseconds (10^-6 seconds)
// * 100 nanoseconds (10^-7 seconds)
// * nanoseconds (10^-9 seconds)
// * timeval structure, 1 microsecond resolution (10^-6 seconds)
// * timespec structure, 1 nanosecond resolution (10^-9 seconds)
// Note that PyTime_t is now specified as int64_t, in nanoseconds.
// (If we need to change this, we'll need new public API with new names.)
// Previously, PyTime_t was configurable (in theory); some comments and code
// might still allude to that.
//
// Integer overflows are detected and raise OverflowError. Conversion to a
// resolution worse than 1 nanosecond is rounded correctly with the requested
// rounding mode. There are 4 rounding modes: floor (towards -inf), ceiling
// (towards +inf), half even and up (away from zero).
// resolution larger than 1 nanosecond is rounded correctly with the requested
// rounding mode. Available rounding modes:
//
// Some functions clamp the result in the range [_PyTime_MIN; _PyTime_MAX], so
// the caller doesn't have to handle errors and doesn't need to hold the GIL.
// For example, _PyTime_Add(t1, t2) computes t1+t2 and clamp the result on
// overflow.
// * Round towards minus infinity (-inf). For example, used to read a clock.
// * Round towards infinity (+inf). For example, used for timeout to wait "at
// least" N seconds.
// * Round to nearest with ties going to nearest even integer. For example, used
// to round from a Python float.
// * Round away from zero. For example, used for timeout.
//
// Some functions clamp the result in the range [PyTime_MIN; PyTime_MAX]. The
// caller doesn't have to handle errors and so doesn't need to hold the GIL to
// handle exceptions. For example, _PyTime_Add(t1, t2) computes t1+t2 and
// clamps the result on overflow.
//
// Clocks:
//
// * System clock
// * Monotonic clock
// * Performance counter
//
// Operations like (t * k / q) with integers are implemented in a way to reduce
// the risk of integer overflow. Such operation is used to convert a clock
// value expressed in ticks with a frequency to _PyTime_t, like
// QueryPerformanceCounter() with QueryPerformanceFrequency().
// Internally, operations like (t * k / q) with integers are implemented in a
// way to reduce the risk of integer overflow. Such operation is used to convert a
// clock value expressed in ticks with a frequency to PyTime_t, like
// QueryPerformanceCounter() with QueryPerformanceFrequency() on Windows.


#ifndef Py_INTERNAL_TIME_H
#define Py_INTERNAL_TIME_H
Expand All @@ -56,14 +62,7 @@ extern "C" {
struct timeval;
#endif

// _PyTime_t: Python timestamp with subsecond precision. It can be used to
// store a duration, and so indirectly a date (related to another date, like
// UNIX epoch).
typedef int64_t _PyTime_t;
// _PyTime_MIN nanoseconds is around -292.3 years
#define _PyTime_MIN INT64_MIN
// _PyTime_MAX nanoseconds is around +292.3 years
#define _PyTime_MAX INT64_MAX
typedef PyTime_t _PyTime_t;
#define _SIZEOF_PYTIME_T 8

typedef enum {
Expand Down Expand Up @@ -147,7 +146,7 @@ PyAPI_FUNC(_PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t
PyAPI_FUNC(_PyTime_t) _PyTime_FromNanoseconds(_PyTime_t ns);

// Create a timestamp from a number of microseconds.
// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
// Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
extern _PyTime_t _PyTime_FromMicrosecondsClamp(_PyTime_t us);

// Create a timestamp from nanoseconds (Python int).
Expand All @@ -169,10 +168,6 @@ PyAPI_FUNC(int) _PyTime_FromMillisecondsObject(_PyTime_t *t,
PyObject *obj,
_PyTime_round_t round);

// Convert a timestamp to a number of seconds as a C double.
// Export for '_socket' shared extension.
PyAPI_FUNC(double) _PyTime_AsSecondsDouble(_PyTime_t t);

// Convert timestamp to a number of milliseconds (10^-3 seconds).
// Export for '_ssl' shared extension.
PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
Expand All @@ -183,9 +178,6 @@ PyAPI_FUNC(_PyTime_t) _PyTime_AsMilliseconds(_PyTime_t t,
PyAPI_FUNC(_PyTime_t) _PyTime_AsMicroseconds(_PyTime_t t,
_PyTime_round_t round);

// Convert timestamp to a number of nanoseconds (10^-9 seconds).
extern _PyTime_t _PyTime_AsNanoseconds(_PyTime_t t);

#ifdef MS_WINDOWS
// Convert timestamp to a number of 100 nanoseconds (10^-7 seconds).
extern _PyTime_t _PyTime_As100Nanoseconds(_PyTime_t t,
Expand Down Expand Up @@ -250,7 +242,7 @@ PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts);
#endif


// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
// Compute t1 + t2. Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2);

// Structure used by time.get_clock_info()
Expand All @@ -267,7 +259,8 @@ typedef struct {
// On integer overflow, silently ignore the overflow and clamp the clock to
// [_PyTime_MIN; _PyTime_MAX].
//
// Use _PyTime_GetSystemClockWithInfo() to check for failure.
// Use _PyTime_GetSystemClockWithInfo or the public PyTime_Time() to check
// for failure.
// Export for '_random' shared extension.
PyAPI_FUNC(_PyTime_t) _PyTime_GetSystemClock(void);

Expand All @@ -287,7 +280,8 @@ extern int _PyTime_GetSystemClockWithInfo(
// On integer overflow, silently ignore the overflow and clamp the clock to
// [_PyTime_MIN; _PyTime_MAX].
//
// Use _PyTime_GetMonotonicClockWithInfo() to check for failure.
// Use _PyTime_GetMonotonicClockWithInfo or the public PyTime_Monotonic()
// to check for failure.
// Export for '_random' shared extension.
PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void);

Expand Down Expand Up @@ -322,10 +316,12 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm);
// On integer overflow, silently ignore the overflow and clamp the clock to
// [_PyTime_MIN; _PyTime_MAX].
//
// Use _PyTime_GetPerfCounterWithInfo() to check for failure.
// Use _PyTime_GetPerfCounterWithInfo() or the public PyTime_PerfCounter
// to check for failure.
// Export for '_lsprof' shared extension.
PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void);


// Get the performance counter: clock with the highest available resolution to
// measure a short duration.
//
Expand All @@ -336,6 +332,13 @@ extern int _PyTime_GetPerfCounterWithInfo(
_PyTime_t *t,
_Py_clock_info_t *info);

// Alias for backward compatibility
#define _PyTime_MIN PyTime_MIN
#define _PyTime_MAX PyTime_MAX
#define _PyTime_AsSecondsDouble PyTime_AsSecondsDouble


// --- _PyDeadline -----------------------------------------------------------

// Create a deadline.
// Pseudo code: _PyTime_GetMonotonicClock() + timeout.
Expand Down
Loading

0 comments on commit 879f454

Please sign in to comment.