mirror of
https://github.com/python/cpython.git
synced 2024-11-24 18:34:43 +08:00
bpo-32025: Add time.thread_time() (#4410)
* bpo-32025: Add time.thread_time() * Add missing #endif * Add NEWS blurb * Add docs and whatsnew * Address review comments * Review comments
This commit is contained in:
parent
762b9571c9
commit
4bd41c9b52
@ -241,6 +241,7 @@ Functions
|
|||||||
* ``'monotonic'``: :func:`time.monotonic`
|
* ``'monotonic'``: :func:`time.monotonic`
|
||||||
* ``'perf_counter'``: :func:`time.perf_counter`
|
* ``'perf_counter'``: :func:`time.perf_counter`
|
||||||
* ``'process_time'``: :func:`time.process_time`
|
* ``'process_time'``: :func:`time.process_time`
|
||||||
|
* ``'thread_time'``: :func:`time.thread_time`
|
||||||
* ``'time'``: :func:`time.time`
|
* ``'time'``: :func:`time.time`
|
||||||
|
|
||||||
The result has the following attributes:
|
The result has the following attributes:
|
||||||
@ -603,6 +604,32 @@ Functions
|
|||||||
of the calendar date may be accessed as attributes.
|
of the calendar date may be accessed as attributes.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: thread_time() -> float
|
||||||
|
|
||||||
|
.. index::
|
||||||
|
single: CPU time
|
||||||
|
single: processor time
|
||||||
|
single: benchmarking
|
||||||
|
|
||||||
|
Return the value (in fractional seconds) of the sum of the system and user
|
||||||
|
CPU time of the current thread. It does not include time elapsed during
|
||||||
|
sleep. It is thread-specific by definition. The reference point of the
|
||||||
|
returned value is undefined, so that only the difference between the results
|
||||||
|
of consecutive calls in the same thread is valid.
|
||||||
|
|
||||||
|
Availability: Windows, Linux, Unix systems supporting
|
||||||
|
``CLOCK_THREAD_CPUTIME_ID``.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: thread_time_ns() -> int
|
||||||
|
|
||||||
|
Similar to :func:`thread_time` but return time as nanoseconds.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
|
||||||
.. function:: time_ns() -> int
|
.. function:: time_ns() -> int
|
||||||
|
|
||||||
Similar to :func:`time` but returns time as an integer number of nanoseconds
|
Similar to :func:`time` but returns time as an integer number of nanoseconds
|
||||||
|
@ -372,6 +372,10 @@ Add new clock identifiers:
|
|||||||
the time the system has been running and not suspended, providing accurate
|
the time the system has been running and not suspended, providing accurate
|
||||||
uptime measurement, both absolute and interval.
|
uptime measurement, both absolute and interval.
|
||||||
|
|
||||||
|
Added functions :func:`time.thread_time` and :func:`time.thread_time_ns`
|
||||||
|
to get per-thread CPU time measurements.
|
||||||
|
(Contributed by Antoine Pitrou in :issue:`32025`.)
|
||||||
|
|
||||||
unittest.mock
|
unittest.mock
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -47,6 +47,12 @@ ROUNDING_MODES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def busy_wait(duration):
|
||||||
|
deadline = time.monotonic() + duration
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TimeTestCase(unittest.TestCase):
|
class TimeTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -81,6 +87,10 @@ class TimeTestCase(unittest.TestCase):
|
|||||||
check_ns(time.process_time(),
|
check_ns(time.process_time(),
|
||||||
time.process_time_ns())
|
time.process_time_ns())
|
||||||
|
|
||||||
|
if hasattr(time, 'thread_time'):
|
||||||
|
check_ns(time.thread_time(),
|
||||||
|
time.thread_time_ns())
|
||||||
|
|
||||||
if hasattr(time, 'clock_gettime'):
|
if hasattr(time, 'clock_gettime'):
|
||||||
check_ns(time.clock_gettime(time.CLOCK_REALTIME),
|
check_ns(time.clock_gettime(time.CLOCK_REALTIME),
|
||||||
time.clock_gettime_ns(time.CLOCK_REALTIME))
|
time.clock_gettime_ns(time.CLOCK_REALTIME))
|
||||||
@ -486,10 +496,57 @@ class TimeTestCase(unittest.TestCase):
|
|||||||
# on Windows
|
# on Windows
|
||||||
self.assertLess(stop - start, 0.020)
|
self.assertLess(stop - start, 0.020)
|
||||||
|
|
||||||
|
# process_time() should include CPU time spent in any thread
|
||||||
|
start = time.process_time()
|
||||||
|
busy_wait(0.100)
|
||||||
|
stop = time.process_time()
|
||||||
|
self.assertGreaterEqual(stop - start, 0.020) # machine busy?
|
||||||
|
|
||||||
|
t = threading.Thread(target=busy_wait, args=(0.100,))
|
||||||
|
start = time.process_time()
|
||||||
|
t.start()
|
||||||
|
t.join()
|
||||||
|
stop = time.process_time()
|
||||||
|
self.assertGreaterEqual(stop - start, 0.020) # machine busy?
|
||||||
|
|
||||||
info = time.get_clock_info('process_time')
|
info = time.get_clock_info('process_time')
|
||||||
self.assertTrue(info.monotonic)
|
self.assertTrue(info.monotonic)
|
||||||
self.assertFalse(info.adjustable)
|
self.assertFalse(info.adjustable)
|
||||||
|
|
||||||
|
def test_thread_time(self):
|
||||||
|
if not hasattr(time, 'thread_time'):
|
||||||
|
if sys.platform.startswith(('linux', 'win')):
|
||||||
|
self.fail("time.thread_time() should be available on %r"
|
||||||
|
% (sys.platform,))
|
||||||
|
else:
|
||||||
|
self.skipTest("need time.thread_time")
|
||||||
|
|
||||||
|
# thread_time() should not include time spend during a sleep
|
||||||
|
start = time.thread_time()
|
||||||
|
time.sleep(0.100)
|
||||||
|
stop = time.thread_time()
|
||||||
|
# use 20 ms because thread_time() has usually a resolution of 15 ms
|
||||||
|
# on Windows
|
||||||
|
self.assertLess(stop - start, 0.020)
|
||||||
|
|
||||||
|
# thread_time() should include CPU time spent in current thread...
|
||||||
|
start = time.thread_time()
|
||||||
|
busy_wait(0.100)
|
||||||
|
stop = time.thread_time()
|
||||||
|
self.assertGreaterEqual(stop - start, 0.020) # machine busy?
|
||||||
|
|
||||||
|
# ...but not in other threads
|
||||||
|
t = threading.Thread(target=busy_wait, args=(0.100,))
|
||||||
|
start = time.thread_time()
|
||||||
|
t.start()
|
||||||
|
t.join()
|
||||||
|
stop = time.thread_time()
|
||||||
|
self.assertLess(stop - start, 0.020)
|
||||||
|
|
||||||
|
info = time.get_clock_info('thread_time')
|
||||||
|
self.assertTrue(info.monotonic)
|
||||||
|
self.assertFalse(info.adjustable)
|
||||||
|
|
||||||
@unittest.skipUnless(hasattr(time, 'clock_settime'),
|
@unittest.skipUnless(hasattr(time, 'clock_settime'),
|
||||||
'need time.clock_settime')
|
'need time.clock_settime')
|
||||||
def test_monotonic_settime(self):
|
def test_monotonic_settime(self):
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
Add time.thread_time() and time.thread_time_ns()
|
@ -1258,6 +1258,112 @@ Process time for profiling as nanoseconds:\n\
|
|||||||
sum of the kernel and user-space CPU time.");
|
sum of the kernel and user-space CPU time.");
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(MS_WINDOWS)
|
||||||
|
#define HAVE_THREAD_TIME
|
||||||
|
static int
|
||||||
|
_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
|
||||||
|
{
|
||||||
|
HANDLE thread;
|
||||||
|
FILETIME creation_time, exit_time, kernel_time, user_time;
|
||||||
|
ULARGE_INTEGER large;
|
||||||
|
_PyTime_t ktime, utime, t;
|
||||||
|
BOOL ok;
|
||||||
|
|
||||||
|
thread = GetCurrentThread();
|
||||||
|
ok = GetThreadTimes(thread, &creation_time, &exit_time,
|
||||||
|
&kernel_time, &user_time);
|
||||||
|
if (!ok) {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info) {
|
||||||
|
info->implementation = "GetThreadTimes()";
|
||||||
|
info->resolution = 1e-7;
|
||||||
|
info->monotonic = 1;
|
||||||
|
info->adjustable = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
large.u.LowPart = kernel_time.dwLowDateTime;
|
||||||
|
large.u.HighPart = kernel_time.dwHighDateTime;
|
||||||
|
ktime = large.QuadPart;
|
||||||
|
|
||||||
|
large.u.LowPart = user_time.dwLowDateTime;
|
||||||
|
large.u.HighPart = user_time.dwHighDateTime;
|
||||||
|
utime = large.QuadPart;
|
||||||
|
|
||||||
|
/* ktime and utime have a resolution of 100 nanoseconds */
|
||||||
|
t = _PyTime_FromNanoseconds((ktime + utime) * 100);
|
||||||
|
*tp = t;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
|
||||||
|
#define HAVE_THREAD_TIME
|
||||||
|
static int
|
||||||
|
_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
const clockid_t clk_id = CLOCK_THREAD_CPUTIME_ID;
|
||||||
|
const char *function = "clock_gettime(CLOCK_THREAD_CPUTIME_ID)";
|
||||||
|
|
||||||
|
if (clock_gettime(clk_id, &ts)) {
|
||||||
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (info) {
|
||||||
|
struct timespec res;
|
||||||
|
info->implementation = function;
|
||||||
|
info->monotonic = 1;
|
||||||
|
info->adjustable = 0;
|
||||||
|
if (clock_getres(clk_id, &res)) {
|
||||||
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
info->resolution = res.tv_sec + res.tv_nsec * 1e-9;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_PyTime_FromTimespec(tp, &ts) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_THREAD_TIME
|
||||||
|
static PyObject *
|
||||||
|
time_thread_time(PyObject *self, PyObject *unused)
|
||||||
|
{
|
||||||
|
_PyTime_t t;
|
||||||
|
if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return _PyFloat_FromPyTime(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(thread_time_doc,
|
||||||
|
"thread_time() -> float\n\
|
||||||
|
\n\
|
||||||
|
Thread time for profiling: sum of the kernel and user-space CPU time.");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
time_thread_time_ns(PyObject *self, PyObject *unused)
|
||||||
|
{
|
||||||
|
_PyTime_t t;
|
||||||
|
if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return _PyTime_AsNanosecondsObject(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(thread_time_ns_doc,
|
||||||
|
"thread_time() -> int\n\
|
||||||
|
\n\
|
||||||
|
Thread time for profiling as nanoseconds:\n\
|
||||||
|
sum of the kernel and user-space CPU time.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
time_get_clock_info(PyObject *self, PyObject *args)
|
time_get_clock_info(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
@ -1311,6 +1417,13 @@ time_get_clock_info(PyObject *self, PyObject *args)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_THREAD_TIME
|
||||||
|
else if (strcmp(name, "thread_time") == 0) {
|
||||||
|
if (_PyTime_GetThreadTimeWithInfo(&t, &info) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
else {
|
else {
|
||||||
PyErr_SetString(PyExc_ValueError, "unknown clock");
|
PyErr_SetString(PyExc_ValueError, "unknown clock");
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -1519,6 +1632,10 @@ static PyMethodDef time_methods[] = {
|
|||||||
{"monotonic_ns", time_monotonic_ns, METH_NOARGS, monotonic_ns_doc},
|
{"monotonic_ns", time_monotonic_ns, METH_NOARGS, monotonic_ns_doc},
|
||||||
{"process_time", time_process_time, METH_NOARGS, process_time_doc},
|
{"process_time", time_process_time, METH_NOARGS, process_time_doc},
|
||||||
{"process_time_ns", time_process_time_ns, METH_NOARGS, process_time_ns_doc},
|
{"process_time_ns", time_process_time_ns, METH_NOARGS, process_time_ns_doc},
|
||||||
|
#ifdef HAVE_THREAD_TIME
|
||||||
|
{"thread_time", time_thread_time, METH_NOARGS, thread_time_doc},
|
||||||
|
{"thread_time_ns", time_thread_time_ns, METH_NOARGS, thread_time_ns_doc},
|
||||||
|
#endif
|
||||||
{"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc},
|
{"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc},
|
||||||
{"perf_counter_ns", time_perf_counter_ns, METH_NOARGS, perf_counter_ns_doc},
|
{"perf_counter_ns", time_perf_counter_ns, METH_NOARGS, perf_counter_ns_doc},
|
||||||
{"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc},
|
{"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc},
|
||||||
|
Loading…
Reference in New Issue
Block a user