8000 Issue #14428, #14397: Implement the PEP 418 · python/cpython@ec89539 · GitHub
[go: up one dir, main page]

Skip to content

Commit ec89539

Browse files
committed
Issue #14428, #14397: Implement the PEP 418
* Rename time.steady() to time.monotonic() * On Windows, time.monotonic() uses GetTickCount/GetTickCount64() instead of QueryPerformanceCounter() * time.monotonic() uses CLOCK_HIGHRES if available * Add time.get_clock_info(), time.perf_counter() and time.process_time() functions
1 parent ca6e40f commit ec89539

File tree

9 files changed

+723
-127
lines changed

9 files changed

+723
-127
lines changed

Doc/library/time.rst

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,30 @@ The module defines the following functions and data items:
155155
.. versionadded:: 3.3
156156

157157

158+
.. class:: clock_info
159+
160+
Clock information object created by :func:`get_clock_info`.
161+
162+
.. attribute:: implementation
163+
164+
name of the underlying C function used to get the clock value
165+
166+
.. attribute:: is_monotonic
167+
168+
``True`` if the clock cannot go backward, ``False`` otherwise
169+
170+
.. attribute:: is_adjusted
171+
172+
``True`` if the clock can be adjusted (e.g. by a NTP daemon),
173+
``False`` otherwise
174+
175+
.. attribute:: resolution
176+
177+
Resolution of the clock in seconds (:class:`float`)
178+
179+
.. versionadded:: 3.3
180+
181+
158182
.. function:: clock_settime(clk_id, time)
159183

160184
Set the time of the specified clock *clk_id*.
@@ -236,6 +260,22 @@ The module defines the following functions and data items:
236260
Nonzero if a DST timezone is defined.
237261

238262

263+
.. function:: get_clock_info(name)
264+
265+
Get information on the specified clock as a :class:`clock_info` object.
266+
267+
Supported clock names:
268+
269+
270+
* ``'clock'``: :func:`time.clock`
271+
* ``'monotonic'``: :func:`time.monotonic`
272+
* ``'perf_counter'``: :func:`time.perf_counter`
273+
* ``'process_time'``: :func:`time.process_time`
274+
* ``'time'``: :func:`time.time`
275+
276+
.. versionadded:: 3.3
277+
278+
239279
.. function:: gmtime([secs])
240280

241281
Convert a time expressed in seconds since the epoch to a :class:`struct_time` in
@@ -265,20 +305,43 @@ The module defines the following functions and data items:
265305
The earliest date for which it can generate a time is platform-dependent.
266306

267307

268-
.. function:: steady(strict=False)
308+
.. function:: monotonic()
309+
310+
Monotonic clock, i.e. cannot go backward. It is not affected by system
311+
clock updates. The reference point of the returned value is undefined, so
312+
that only the difference between the results of consecutive calls is valid
313+
and is a number of seconds.
314+
315+
On Windows versions older than Vista, :func:`monotonic` detects
316+
:c:func:`GetTickCount` integer overflow (32 bits, roll-over after 49.7
317+
days). It increases an internal epoch (reference time by) 2\ :sup:`32` each
318+
time that an overflow is detected. The epoch is stored in the process-local
319+
state and so the value of :func:`monotonic` may be different in two Python
320+
processes running for more than 49 days. On more recent versions of Windows
321+
and on other operating systems, :func:`monotonic` is system-wide.
322+
323+
Availability: Windows, Mac OS X, Linux, FreeBSD, OpenBSD, Solaris.
324+
325+
.. versionadded:: 3.3
326+
327+
328+
.. function:: perf_counter()
329+
330+
Performance counter with the highest available resolution to measure a short
331+
duration. It does include time elapsed during sleep and is system-wide.
332+
The reference point of the returned value is undefined, so that only the
333+
difference between the results of consecutive calls is valid and is a number
334+
of seconds.
335+
336+
.. versionadded:: 3.3
269337

270-
.. index::
271-
single: benchmarking
272338

273-
Return the current time as a floating point number expressed in seconds.
274-
This clock advances at a steady rate relative to real time and it may not be
275-
adjusted. The reference point of the returned value is undefined so only the
276-
difference of consecutive calls is valid.
339+
.. function:: process_time()
277340

278-
If available, a monotonic clock is used. By default,
279-
the function falls back to another clock if the monotonic clock failed or is
280-
not available. If *strict* is True, raise an :exc:`OSError` on error or
281-
:exc:`NotImplementedError` if no monotonic clock is available.
341+
Sum of the system and user CPU time of the current process. It does not
342+
include time elapsed during sleep. It is process-wide by definition. The
343+
reference point of the returned value is undefined, so that only the
344+
difference between the results of consecutive calls is valid.
282345

283346
.. versionadded:: 3.3
284347

Doc/whatsnew/3.3.rst

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,13 +1059,21 @@ sys
10591059
time
10601060
----
10611061

1062-
The :mod:`time` module has new functions:
1062+
The :pep:`418` added new functions to the :mod:`time` module:
10631063

1064-
* :func:`~time.clock_getres` and :func:`~time.clock_gettime` functions and
1065-
``CLOCK_xxx`` constants.
1066-
* :func:`~time.steady`.
1064+
* :func:`~time.get_clock_info`: Get information on a clock.
1065+
* :func:`~time.monotonic`: Monotonic clock (cannot go backward), not affected
1066+
by system clock updates.
1067+
* :func:`~time.perf_counter`: Performance counter with the highest available
1068+
resolution to measure a short duration.
1069+
* :func:`~time.process_time`: Sum of the system and user CPU time of the
1070+
current process.
10671071

1068-
(Contributed by Victor Stinner in :issue:`10278`)
1072+
Other new functions:
1073+
1074+
* :func:`~time.clock_getres`, :func:`~time.clock_gettime` and
1075+
:func:`~time.clock_settime` functions with ``CLOCK_xxx`` constants.
1076+
(Contributed by Victor Stinner in :issue:`10278`)
10691077

10701078

10711079
types

Include/pytime.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,25 @@ typedef struct {
2222
} _PyTime_timeval;
2323
#endif
2424

25+
/* Structure used by time.get_clock_info() */
26+
typedef struct {
27+
const char *implementation;
28+
int is_monotonic;
29+
int is_adjusted;
30+
double resolution;
31+
} _Py_clock_info_t;
32+
2533
/* Similar to POSIX gettimeofday but cannot fail. If system gettimeofday
2634
* fails or is not available, fall back to lower resolution clocks.
2735
*/
2836
PyAPI_FUNC(void) _PyTime_gettimeofday(_PyTime_timeval *tp);
2937

38+
/* Similar to _PyTime_gettimeofday() but retrieve also information on the
39+
* clock used to get the current time. */
40+
PyAPI_FUNC(void) _PyTime_gettimeofday_info(
41+
_PyTime_timeval *tp,
42+
_Py_clock_info_t *info);
43+
3044
#define _PyTime_ADD_SECONDS(tv, interval) \
3145
do { \
3246
tv.tv_usec += (long) (((long) interval - interval) * 1000000); \

Lib/queue.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
import dummy_threading as threading
77
from collections import deque
88
from heapq import heappush, heappop
9-
from time import steady as time
9+
try:
10+
from time import monotonic as time
11+
except ImportError:
12+
from time import time
1013

1114
__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue']
1215

Lib/test/test_time.py

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import sysconfig
66
import sys
77
import platform
8+
try:
9+
import threading
10+
except ImportError:
11+
threading = None
812

913
# Max year is only limited by the size of C int.
1014
SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4
@@ -23,9 +27,20 @@ def test_data_attributes(self):
2327
time.timezone
2428
time.tzname
2529

30+
def test_time(self):
31+
time.time()
32+
info = time.get_clock_info('time')
33+
self.assertEqual(info.is_monotonic, False)
34+
if sys.platform != 'win32':
35+
self.assertEqual(info.is_adjusted, True)
36+
2637
def test_clock(self):
2738
time.clock()
2839

40+
info = time.get_clock_info('clock')
41+
self.assertEqual(info.is_monotonic, True)
42+
self.assertEqual(info.is_adjusted, False)
43+
2944
@unittest.skipUnless(hasattr(time, 'clock_gettime'),
3045
'need time.clock_gettime()')
3146
def test_clock_realtime(self):
@@ -56,7 +71,9 @@ def test_clock_settime(self):
5671
except PermissionError:
5772
pass
5873

59-
self.assertRaises(OSError, time.clock_settime, time.CLOCK_MONOTONIC, 0)
74+
if hasattr(time, 'CLOCK_MONOTONIC'):
75+
self.assertRaises(OSError,
76+
time.clock_settime, time.CLOCK_MONOTONIC, 0)
6077

6178
def test_conversions(self):
6279
self.assertEqual(time.ctime(self.t),
@@ -342,23 +359,69 @@ def test_mktime_error(self):
342359
pass
343360
self.assertEqual(time.strftime('%Z', tt), tzname)
344361

345-
def test_steady(self):
346-
t1 = time.steady()
362+
@unittest.skipUnless(hasattr(time, 'monotonic'),
363+
'need time.monotonic')
364+
def test_monotonic(self):
365+
t1 = time.monotonic()
347366
time.sleep(0.1)
348-
t2 = time.steady()
367+
t2 = time.monotonic()
349368
dt = t2 - t1
350-
# may fail if the system clock was changed
351369
self.assertGreater(t2, t1)
352370
self.assertAlmostEqual(dt, 0.1, delta=0.2)
353371

354-
def test_steady_strict(self):
372+
info = time.get_clock_info('monotonic')
373+
self.assertEqual(info.is_monotonic, True)
374+
if sys.platform == 'linux':
375+
self.assertEqual(info.is_adjusted, True)
376+
else:
377+
self.assertEqual(info.is_adjusted, False)
378+
379+
def test_perf_counter(self):
380+
time.perf_counter()
381+
382+
def test_process_time(self):
383+
start = time.process_time()
384+
time.sleep(0.1)
385+
stop = time.process_time()
386+
self.assertLess(stop - start, 0.01)
387+
388+
info = time.get_clock_info('process_time')
389+
self.assertEqual(info.is_monotonic, True)
390+
self.assertEqual(info.is_adjusted, False)
391+
392+
@unittest.skipUnless(threading,
393+
'need threading')
394+
def test_process_time_threads(self):
395+
class BusyThread(threading.Thread):
396+
def run(self):
397+
while not self.stop:
398+
pass
399+
400+
thread = BusyThread()
401+
thread.stop = False
402+
t1 = time.process_time()
403+
thread.start()
404+
time.sleep(0.2)
405+
t2 = time.process_time()
406+
thread.stop = True
407+
thread.join()
408+
self.assertGreater(t2 - t1, 0.1)
409+
410+
@unittest.skipUnless(hasattr(time, 'monotonic'),
411+
'need time.monotonic')
412+
@unittest.skipUnless(hasattr(time, 'clock_settime'),
413+
'need time.clock_settime')
414+
def test_monotonic_settime(self):
415+
t1 = time.monotonic()
416+
realtime = time.clock_gettime(time.CLOCK_REALTIME)
417+
# jump backward with an offset of 1 hour
355418
try:
356-
t1 = time.steady(strict=True)
357-
except OSError as err:
358-
self.skipTest("the monotonic clock failed: %s" % err)
359-
except NotImplementedError:
360-
self.skipTest("no monotonic clock available")
361-
t2 = time.steady(strict=True)
419+
time.clock_settime(time.CLOCK_REALTIME, realtime - 3600)
420+
except PermissionError as err:
421+
self.skipTest(err)
422+
t2 = time.monotonic()
423+
time.clock_settime(time.CLOCK_REALTIME, realtime)
424+
# monotonic must not be affected by system clock updates
362425
self.assertGreaterEqual(t2, t1)
363426

364427
def test_localtime_failure(self):
@@ -378,6 +441,26 @@ def test_localtime_failure(self):
378441
self.assertRaises(OSError, time.localtime, invalid_time_t)
379442
self.assertRaises(OSError, time.ctime, invalid_time_t)
380443

444+
def test_get_clock_info(self):
445+
clocks = ['clock', 'perf_counter', 'process_time', 'time']
446+
if hasattr(time, 'monotonic'):
447+
clocks.append('monotonic')
448+
449+
for name in clocks:
450+
info = time.get_clock_info(nam 10000 e)
451+
#self.assertIsInstance(info, dict)
452+
self.assertIsInstance(info.implementation, str)
453+
self.assertNotEqual(info.implementation, '')
454+
self.assertIsInstance(info.is_monotonic, bool)
455+
self.assertIsInstance(info.resolution, float)
456+
# 0.0 < resolution <= 1.0
457+
self.assertGreater(info.resolution, 0.0)
458+
self.assertLessEqual(info.resolution, 1.0)
459+
self.assertIsInstance(info.is_adjusted, bool)
460+
461+
self.assertRaises(ValueError, time.get_clock_info, 'xxx')
462+
463+
381464
class TestLocale(unittest.TestCase):
382465
def setUp(self):
383466
self.oldloc = locale.setlocale(locale.LC_ALL)

Lib/threading.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import sys as _sys
44
import _thread
55

6-
from time import steady as _time, sleep as _sleep
6+
from time import sleep as _sleep
7+
try:
8+
from time import monotonic as _time
9+
except ImportError:
10+
from time import time as _time
711
from traceback import format_exc as _format_exc
812
from _weakrefset import WeakSet
913

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ Core and Builtins
8181
Library
8282
-------
8383

84+
- Issue #14428: Implement the PEP 418. Add time.get_clock_info(),
85+
time.perf_counter() and time.process_time() functions, and rename
86+
time.steady() to time.monotonic().
87+
8488
- Issue #14646: importlib.util.module_for_loader() now sets __loader__ and
8589
__package__ (when possible).
8690

0 commit comments

Comments
 (0)
0