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`
|
||||
* ``'perf_counter'``: :func:`time.perf_counter`
|
||||
* ``'process_time'``: :func:`time.process_time`
|
||||
* ``'thread_time'``: :func:`time.thread_time`
|
||||
* ``'time'``: :func:`time.time`
|
||||
|
||||
The result has the following attributes:
|
||||
@ -603,6 +604,32 @@ Functions
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
-------------
|
||||
|
||||
|
@ -47,6 +47,12 @@ ROUNDING_MODES = (
|
||||
)
|
||||
|
||||
|
||||
def busy_wait(duration):
|
||||
deadline = time.monotonic() + duration
|
||||
while time.monotonic() < deadline:
|
||||
pass
|
||||
|
||||
|
||||
class TimeTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -81,6 +87,10 @@ class TimeTestCase(unittest.TestCase):
|
||||
check_ns(time.process_time(),
|
||||
time.process_time_ns())
|
||||
|
||||
if hasattr(time, 'thread_time'):
|
||||
check_ns(time.thread_time(),
|
||||
time.thread_time_ns())
|
||||
|
||||
if hasattr(time, 'clock_gettime'):
|
||||
check_ns(time.clock_gettime(time.CLOCK_REALTIME),
|
||||
time.clock_gettime_ns(time.CLOCK_REALTIME))
|
||||
@ -486,10 +496,57 @@ class TimeTestCase(unittest.TestCase):
|
||||
# on Windows
|
||||
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')
|
||||
self.assertTrue(info.monotonic)
|
||||
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'),
|
||||
'need time.clock_settime')
|
||||
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.");
|
||||
|
||||
|
||||
#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 *
|
||||
time_get_clock_info(PyObject *self, PyObject *args)
|
||||
{
|
||||
@ -1311,6 +1417,13 @@ time_get_clock_info(PyObject *self, PyObject *args)
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_THREAD_TIME
|
||||
else if (strcmp(name, "thread_time") == 0) {
|
||||
if (_PyTime_GetThreadTimeWithInfo(&t, &info) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
PyErr_SetString(PyExc_ValueError, "unknown clock");
|
||||
return NULL;
|
||||
@ -1519,6 +1632,10 @@ static PyMethodDef time_methods[] = {
|
||||
{"monotonic_ns", time_monotonic_ns, METH_NOARGS, monotonic_ns_doc},
|
||||
{"process_time", time_process_time, METH_NOARGS, process_time_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_ns", time_perf_counter_ns, METH_NOARGS, perf_counter_ns_doc},
|
||||
{"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc},
|
||||
|
Loading…
Reference in New Issue
Block a user