From 62573ccda952674fb7da3112f110285e570cf6ea Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 2 Nov 2023 11:52:15 +0100 Subject: [PATCH] gh-111482: Use Argument Clinic for clock_gettime() Use Argument Clinic for time.clock_gettime() and time.clock_gettime_ns() functions. Benchmark: 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 --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + ...-11-02-12-15-46.gh-issue-111482.FWqZIU.rst | 3 + Modules/_testinternalcapi.c | 39 ++++++++ Modules/clinic/timemodule.c.h | 74 ++++++++++++++ Modules/timemodule.c | 97 +++++++++++++------ 8 files changed, 187 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/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 0808076f44de310..f614cfdf22de490 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -833,6 +833,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(certfile)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(check_same_thread)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(clear)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(clk_id)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(close)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(closed)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(closefd)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 8d22a9ba261010c..5330c2ea4a9143e 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -322,6 +322,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(certfile) STRUCT_FOR_ID(check_same_thread) STRUCT_FOR_ID(clear) + STRUCT_FOR_ID(clk_id) STRUCT_FOR_ID(close) STRUCT_FOR_ID(closed) STRUCT_FOR_ID(closefd) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index d41a7478db663fd..e960d29f54e0fb5 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -831,6 +831,7 @@ extern "C" { INIT_ID(certfile), \ INIT_ID(check_same_thread), \ INIT_ID(clear), \ + INIT_ID(clk_id), \ INIT_ID(close), \ INIT_ID(closed), \ INIT_ID(closefd), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 0c02e902b308e31..ddc3e82d081aa98 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -807,6 +807,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(clear); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(clk_id); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(close); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); 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/_testinternalcapi.c b/Modules/_testinternalcapi.c index a71e7e1dcc12565..692d008cdf8121c 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1639,6 +1639,44 @@ perf_trampoline_set_persist_after_fork(PyObject *self, PyObject *args) } +static PyObject * +bench_asutf8(PyObject *self, PyObject *args) +{ + PyObject *str; + Py_ssize_t loops; + if (!PyArg_ParseTuple(args, "O!n", &PyUnicode_Type, &str, &loops)) { + return NULL; + } + + PyObject *codecs = PyImport_ImportModule("_codecs"); + if (codecs == NULL) { + return NULL; + } + PyObject *lookup_error = PyObject_GetAttrString(codecs, "lookup_error"); + Py_DECREF(codecs); + if (lookup_error == NULL) { + return NULL; + } + + _PyTime_t t = _PyTime_GetPerfCounter(); + for (Py_ssize_t i=0; i < loops; i++) { + PyObject *error = PyObject_CallOneArg(lookup_error, str); + if (error != NULL) { + Py_DECREF(error); + } + else { + PyErr_Clear(); + } + } + + _PyTime_t dt = _PyTime_GetPerfCounter() - t; + + Py_DECREF(lookup_error); + + return PyFloat_FromDouble(_PyTime_AsSecondsDouble(dt)); +} + + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -1701,6 +1739,7 @@ static PyMethodDef module_functions[] = { {"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS}, _TESTINTERNALCAPI_WRITE_UNRAISABLE_EXC_METHODDEF _TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF + {"bench_asutf8", bench_asutf8, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; 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},