Skip to content

Commit

Permalink
pythongh-111482: Use Argument Clinic for clock_gettime()
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vstinner committed Nov 2, 2023
1 parent 6a0d7b4 commit 62573cc
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 32 deletions.
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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.
39 changes: 39 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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 */
};

Expand Down
74 changes: 74 additions & 0 deletions Modules/clinic/timemodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 65 additions & 32 deletions Modules/timemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -227,62 +233,85 @@ _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;
}
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
Expand Down Expand Up @@ -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},
Expand Down

0 comments on commit 62573cc

Please sign in to comment.