From 43a07f3adc8454cea752cc9e1ec9b8ce9ada2295 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 2 Nov 2023 14:29:05 +0100 Subject: [PATCH] gh-111482: Use Argument Clinic for clock_gettime() (#111641) Use Argument Clinic for time.clock_gettime() and time.clock_gettime_ns() functions. Benchmark on time.clock_gettime_ns(): import time import pyperf runner = pyperf.Runner() runner.timeit( 'clock_gettime_ns(CLOCK_MONOTONIC_COARSE)', setup='import time; clock_gettime_ns=time.clock_gettime_ns; CLOCK_MONOTONIC_COARSE=6', stmt='clock_gettime_ns(CLOCK_MONOTONIC_COARSE)') Result on Linux with CPU isolation: Mean +- std dev: [ref] 134 ns +- 1 ns -> [change] 55.7 ns +- 1.4 ns: 2.41x faster --- ...-11-02-12-15-46.gh-issue-111482.FWqZIU.rst | 3 + Modules/clinic/timemodule.c.h | 74 ++++++++++++++ Modules/timemodule.c | 97 +++++++++++++------ 3 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-02-12-15-46.gh-issue-111482.FWqZIU.rst create mode 100644 Modules/clinic/timemodule.c.h diff --git a/Misc/NEWS.d/next/Library/2023-11-02-12-15-46.gh-issue-111482.FWqZIU.rst b/Misc/NEWS.d/next/Library/2023-11-02-12-15-46.gh-issue-111482.FWqZIU.rst new file mode 100644 index 000000000000000..d73e45ccf09b480 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-02-12-15-46.gh-issue-111482.FWqZIU.rst @@ -0,0 +1,3 @@ +:mod:`time`: Make :func:`time.clock_gettime()` and +:func:`time.clock_gettime_ns()` functions up to 2x faster by faster calling +convention. Patch by Victor Stinner. diff --git a/Modules/clinic/timemodule.c.h b/Modules/clinic/timemodule.c.h new file mode 100644 index 000000000000000..bbc0748f9a9c0dd --- /dev/null +++ b/Modules/clinic/timemodule.c.h @@ -0,0 +1,74 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#if defined(HAVE_CLOCK_GETTIME) + +PyDoc_STRVAR(time_clock_gettime__doc__, +"clock_gettime($module, clk_id, /)\n" +"--\n" +"\n" +"Return the time of the specified clock clk_id as a float."); + +#define TIME_CLOCK_GETTIME_METHODDEF \ + {"clock_gettime", (PyCFunction)time_clock_gettime, METH_O, time_clock_gettime__doc__}, + +static PyObject * +time_clock_gettime_impl(PyObject *module, clockid_t clk_id); + +static PyObject * +time_clock_gettime(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + clockid_t clk_id; + + if (!time_clockid_converter(arg, &clk_id)) { + goto exit; + } + return_value = time_clock_gettime_impl(module, clk_id); + +exit: + return return_value; +} + +#endif /* defined(HAVE_CLOCK_GETTIME) */ + +#if defined(HAVE_CLOCK_GETTIME) + +PyDoc_STRVAR(time_clock_gettime_ns__doc__, +"clock_gettime_ns($module, clk_id, /)\n" +"--\n" +"\n" +"Return the time of the specified clock clk_id as nanoseconds (int)."); + +#define TIME_CLOCK_GETTIME_NS_METHODDEF \ + {"clock_gettime_ns", (PyCFunction)time_clock_gettime_ns, METH_O, time_clock_gettime_ns__doc__}, + +static PyObject * +time_clock_gettime_ns_impl(PyObject *module, clockid_t clk_id); + +static PyObject * +time_clock_gettime_ns(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + clockid_t clk_id; + + if (!time_clockid_converter(arg, &clk_id)) { + goto exit; + } + return_value = time_clock_gettime_ns_impl(module, clk_id); + +exit: + return return_value; +} + +#endif /* defined(HAVE_CLOCK_GETTIME) */ + +#ifndef TIME_CLOCK_GETTIME_METHODDEF + #define TIME_CLOCK_GETTIME_METHODDEF +#endif /* !defined(TIME_CLOCK_GETTIME_METHODDEF) */ + +#ifndef TIME_CLOCK_GETTIME_NS_METHODDEF + #define TIME_CLOCK_GETTIME_NS_METHODDEF +#endif /* !defined(TIME_CLOCK_GETTIME_NS_METHODDEF) */ +/*[clinic end generated code: output=b589a2132aa9df47 input=a9049054013a1b77]*/ diff --git a/Modules/timemodule.c b/Modules/timemodule.c index bf48c89f343948c..e82f6eb98ebaf35 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -63,6 +63,12 @@ #define SEC_TO_NS (1000 * 1000 * 1000) +/*[clinic input] +module time +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=a668a08771581f36]*/ + + #if defined(HAVE_TIMES) || defined(HAVE_CLOCK) static int check_ticks_per_second(long tps, const char *context) @@ -227,23 +233,52 @@ _PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #pragma clang diagnostic ignored "-Wunguarded-availability" #endif -static PyObject * -time_clock_gettime(PyObject *self, PyObject *args) +static int +time_clockid_converter(PyObject *obj, clockid_t *p) { - int ret; - struct timespec tp; - #if defined(_AIX) && (SIZEOF_LONG == 8) - long clk_id; - if (!PyArg_ParseTuple(args, "l:clock_gettime", &clk_id)) { + long clk_id = PyLong_AsLong(obj); #else - int clk_id; - if (!PyArg_ParseTuple(args, "i:clock_gettime", &clk_id)) { + int clk_id = PyLong_AsInt(obj); #endif - return NULL; + if (clk_id == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_TypeError, + "clk_id should be integer, not %s", + _PyType_Name(Py_TYPE(obj))); + return 0; } - ret = clock_gettime((clockid_t)clk_id, &tp); + // Make sure that we picked the right type (check sizes type) + Py_BUILD_ASSERT(sizeof(clk_id) == sizeof(*p)); + *p = (clockid_t)clk_id; + return 1; +} + +/*[python input] + +class clockid_t_converter(CConverter): + type = "clockid_t" + converter = 'time_clockid_converter' + +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=53867111501f46c8]*/ + + +/*[clinic input] +time.clock_gettime + + clk_id: clockid_t + / + +Return the time of the specified clock clk_id as a float. +[clinic start generated code]*/ + +static PyObject * +time_clock_gettime_impl(PyObject *module, clockid_t clk_id) +/*[clinic end generated code: output=832b9ebc03328020 input=7e89fcc42ca15e5d]*/ +{ + struct timespec tp; + int ret = clock_gettime(clk_id, &tp); if (ret != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; @@ -251,38 +286,32 @@ time_clock_gettime(PyObject *self, PyObject *args) return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); } -PyDoc_STRVAR(clock_gettime_doc, -"clock_gettime(clk_id) -> float\n\ -\n\ -Return the time of the specified clock clk_id."); +/*[clinic input] +time.clock_gettime_ns + + clk_id: clockid_t + / + +Return the time of the specified clock clk_id as nanoseconds (int). +[clinic start generated code]*/ static PyObject * -time_clock_gettime_ns(PyObject *self, PyObject *args) +time_clock_gettime_ns_impl(PyObject *module, clockid_t clk_id) +/*[clinic end generated code: output=4a045c3a36e60044 input=aabc248db8c8e3e5]*/ { - 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); + int ret = clock_gettime(clk_id, &ts); if (ret != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } + + _PyTime_t t; 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 @@ -1857,12 +1886,16 @@ init_timezone(PyObject *m) } +// Include Argument Clinic code after defining converters such as +// time_clockid_converter(). +#include "clinic/timemodule.c.h" + static PyMethodDef time_methods[] = { {"time", time_time, METH_NOARGS, time_doc}, {"time_ns", time_time_ns, METH_NOARGS, time_ns_doc}, #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}, + TIME_CLOCK_GETTIME_METHODDEF + TIME_CLOCK_GETTIME_NS_METHODDEF #endif #ifdef HAVE_CLOCK_SETTIME {"clock_settime", time_clock_settime, METH_VARARGS, clock_settime_doc},