Add six new “nanosecond” variants of existing functions to thetimemodule:clock_gettime_ns(),clock_settime_ns(),monotonic_ns(),perf_counter_ns(),process_time_ns() andtime_ns(). While similar to the existing functions without the_ns suffix, they provide nanosecond resolution: they return a number ofnanoseconds as a Pythonint.
Thetime.time_ns() resolution is 3 times better than thetime.time()resolution on Linux and Windows.
The clocks resolution of desktop and laptop computers is getting closerto nanosecond resolution. More and more clocks have a frequency in MHz,up to GHz for the CPU TSC clock.
The Pythontime.time() function returns the current time as afloating-point number which is usually a 64-bit binary floating-pointnumber (in the IEEE 754 format).
The problem is that thefloat type starts to lose nanoseconds after 104days. Converting from nanoseconds (int) to seconds (float) andthen back to nanoseconds (int) to check if conversions loseprecision:
# no precision loss>>>x=2**52+1;int(float(x*1e-9)*1e9)-x0# precision loss! (1 nanosecond)>>>x=2**53+1;int(float(x*1e-9)*1e9)-x-1>>>print(datetime.timedelta(seconds=2**53/1e9))104days,5:59:59.254741
time.time() returns seconds elapsed since the UNIX epoch: January1st, 1970. This function hasn’t had nanosecond precision since May 1970(47 years ago):
>>>importdatetime>>>unix_epoch=datetime.datetime(1970,1,1)>>>print(unix_epoch+datetime.timedelta(seconds=2**53/1e9))1970-04-15 05:59:59.254741
Five years ago, thePEP 410 proposed a large and complex change in allPython functions returning time to support nanosecond resolution usingthedecimal.Decimal type.
The PEP was rejected for different reasons:
decimal.Decimal type is uncommon in Python and so requiresto adapt code to handle it.A server is running for longer than 104 days. A clock is read before andafter running a function to measure its performance to detectperformance issues at runtime. Such benchmark only loses precisionbecause of the float type used by clocks, not because of the clockresolution.
On Python microbenchmarks, it is common to see function calls takingless than 100 ns. A difference of a few nanoseconds might becomesignificant.
Two programs “A” and “B” are running on the same system and use the systemclock. The program A reads the system clock with nanosecond resolutionand writes a timestamp with nanosecond resolution. The program B readsthe timestamp with nanosecond resolution, but compares it to the systemclock read with a worse resolution. To simplify the example, let’s saythat B reads the clock with second resolution. If that case, there is awindow of 1 second while the program B can see the timestamp written by Aas “in the future”.
Nowadays, more and more databases and filesystems support storing timeswith nanosecond resolution.
Note
This issue was already fixed for file modification time by adding thest_mtime_ns field to theos.stat() result, and by acceptingnanoseconds inos.utime(). This PEP proposes to generalize thefix.
Since thePEP 410 was rejected:
os.stat_result structure got 3 new fields for timestamps asnanoseconds (Pythonint):st_atime_ns,st_ctime_nsandst_mtime_ns.time.monotonic(),time.perf_counter() andtime.process_time()._PyTime_t type: simple 64-bit signed integer (Cint64_t).The_PyTime_t unit is an implementation detail and not part of theAPI. The unit is currently1nanosecond.Theos.stat_result structure has 3 fields for timestamps asnanoseconds (int):st_atime_ns,st_ctime_ns andst_mtime_ns.
Thens parameter of theos.utime() function accepts a(atime_ns:int,mtime_ns:int) tuple: nanoseconds.
This PEP adds six new functions to thetime module:
time.clock_gettime_ns(clock_id)time.clock_settime_ns(clock_id,time:int)time.monotonic_ns()time.perf_counter_ns()time.process_time_ns()time.time_ns()These functions are similar to the version without the_ns suffix,but return a number of nanoseconds as a Pythonint.
For example,time.monotonic_ns()==int(time.monotonic()*1e9) ifmonotonic() value is small enough to not lose precision.
These functions are needed because they may return “large” timestamps,liketime.time() which uses the UNIX epoch as reference, and so theirfloat-returning variants are likely to lose precision at the nanosecondresolution.
Since thetime.clock() function was deprecated in Python 3.3, notime.clock_ns() is added.
Python has other time-returning functions. No nanosecond variant isproposed for these other functions, either because their internalresolution is greater or equal to 1 us, or because their maximum valueis small enough to not lose precision. For example, the maximum value oftime.clock_getres() should be 1 second.
Examples of unchanged functions:
os module:sched_rr_get_interval(),times(),wait3()andwait4()resource module:ru_utime andru_stime fields ofgetrusage()signal module:getitimer(),setitimer()time module:clock_getres()See also theAnnex: Clocks Resolution in Python.
A new nanosecond-returning flavor of these functions may be added laterif an operating system exposes new functions providing better resolution.
time.time_ns() API is not theoretically future-proof: if clockresolutions continue to increase below the nanosecond level, new Pythonfunctions may be needed.
In practice, the 1 nanosecond resolution is currently enough for allstructures returned by all common operating systems functions.
Hardware clocks with a resolution better than 1 nanosecond alreadyexist. For example, the frequency of a CPU TSC clock is the CPU basefrequency: the resolution is around 0.3 ns for a CPU running at 3GHz. Users who have access to such hardware and really needsub-nanosecond resolution can however extend Python for their needs.Such a rare use case doesn’t justify to design the Python standard libraryto support sub-nanosecond resolution.
For the CPython implementation, nanosecond resolution is convenient: thestandard and well supportedint64_t type can be used to store ananosecond-precise timestamp. It supports a timespan of -292 yearsto +292 years. Using the UNIX epoch as reference, it therefore supportsrepresenting times since year 1677 to year 2262:
>>>1970-2**63/(10**9*3600*24*365.25)1677.728976954687>>>1970+2**63/(10**9*3600*24*365.25)2262.271023045313
It was proposed to modifytime.time() to return a different numbertype with better precision.
ThePEP 410 proposed to returndecimal.Decimal which already exists andsupports arbitrary precision, but it was rejected. Apart fromdecimal.Decimal, no portable real number type with better precisionis currently available in Python.
Changing the built-in Pythonfloat type is out of the scope of thisPEP.
Moreover, changing existing functions to return a new type introduces arisk of breaking the backward compatibility even if the new type isdesigned carefully.
Many ideas of new types were proposed to support larger or arbitraryprecision: fractions, structures or 2-tuple using integers,fixed-point number, etc.
See also thePEP 410 for a previous long discussion on other types.
Adding a new type requires more effort to support it, than reusingthe existingint type. The standard library, third party code andapplications would have to be modified to support it.
The Pythonint type is well known, well supported, easy tomanipulate, and supports all arithmetic operations such asdt=t2-t1.
Moreover, taking/returning an integer number of nanoseconds is not anew concept in Python, as witnessed byos.stat_result andos.utime(ns=(atime_ns,mtime_ns)).
Note
If the Pythonfloat type becomes larger (e.g. decimal128 orfloat128), thetime.time() precision will increase as well.
Thetime.time(ns=False) API was proposed to avoid adding newfunctions. It’s an uncommon (and bad?) programming practice in Python tochange the result type depending on a parameter.
Different options were proposed to allow the user to choose the timeresolution. If each Python module uses a different resolution, it canbecome difficult to handle different resolutions, instead of justseconds (time.time() returningfloat) and nanoseconds(time.time_ns() returningint). Moreover, as written above,there is no need for resolution better than 1 nanosecond in practice inthe Python standard library.
It was proposed to add a newtime_ns module containing the followingfunctions:
time_ns.clock_gettime(clock_id)time_ns.clock_settime(clock_id,time:int)time_ns.monotonic()time_ns.perf_counter()time_ns.process_time()time_ns.time()The first question is whether thetime_ns module should expose exactlythe same API (constants, functions, etc.) as thetime module. It can bepainful to maintain two flavors of thetime module. How are users usesupposed to make a choice between these two modules?
If tomorrow, other nanosecond variants are needed in theos module,will we have to add a newos_ns module as well? There are functionsrelated to time in many modules:time,os,signal,resource,select, etc.
Another idea is to add atime.ns submodule or a nested-namespace toget thetime.ns.time() syntax, but it suffers from the same issues.
This annex contains the resolution of clocks as measured in Python, andnot the resolution announced by the operating system or the resolution ofthe internal structure used by the operating system.
Example of script to measure the smallest difference between twotime.time() andtime.time_ns() reads ignoring differences of zero:
importmathimporttimeLOOPS=10**6print("time.time_ns():%s"%time.time_ns())print("time.time():%s"%time.time())min_dt=[abs(time.time_ns()-time.time_ns())for_inrange(LOOPS)]min_dt=min(filter(bool,min_dt))print("min time_ns() delta:%s ns"%min_dt)min_dt=[abs(time.time()-time.time())for_inrange(LOOPS)]min_dt=min(filter(bool,min_dt))print("min time() delta:%s ns"%math.ceil(min_dt*1e9))
Clocks resolution measured in Python on Fedora 26 (kernel 4.12):
| Function | Resolution |
|---|---|
| clock() | 1 us |
| monotonic() | 81 ns |
| monotonic_ns() | 84 ns |
| perf_counter() | 82 ns |
| perf_counter_ns() | 84 ns |
| process_time() | 2 ns |
| process_time_ns() | 1 ns |
| resource.getrusage() | 1 us |
| time() | 239 ns |
| time_ns() | 84 ns |
| times().elapsed | 10 ms |
| times().user | 10 ms |
Notes on resolutions:
clock() frequency isCLOCKS_PER_SECOND which is 1,000,000 Hz(1 MHz): resolution of 1 us.times() frequency isos.sysconf("SC_CLK_TCK") (or theHZconstant) which is equal to 100 Hz: resolution of 10 ms.resource.getrusage(),os.wait3() andos.wait4() use theru_usage structure. The type of theru_usage.ru_utime andru_usage.ru_stime fields is thetimeval structure which has aresolution of 1 us.Clocks resolution measured in Python on Windows 8.1:
| Function | Resolution |
|---|---|
| monotonic() | 15 ms |
| monotonic_ns() | 15 ms |
| perf_counter() | 100 ns |
| perf_counter_ns() | 100 ns |
| process_time() | 15.6 ms |
| process_time_ns() | 15.6 ms |
| time() | 894.1 us |
| time_ns() | 318 us |
The frequency ofperf_counter() andperf_counter_ns() comes fromQueryPerformanceFrequency(). The frequency is usually 10 MHz: resolution of100 ns. In old Windows versions, the frequency was sometimes 3,579,545 Hz (3.6MHz): resolution of 279 ns.
The resolution oftime.time_ns() is much better thantime.time():84 ns (2.8x better) vs 239 ns on Linux and 318 us(2.8x better) vs 894 us on Windows. Thetime.time() resolution willonly become larger (worse) as years pass since every day adds86,400,000,000,000 nanoseconds to the system clock, which increases theprecision loss.
The difference betweentime.perf_counter(),time.monotonic(),time.process_time() and their respective nanosecond variants isnot visible in this quick script since the script runs for less than 1minute, and the uptime of the computer used to run the script wassmaller than 1 week. A significant difference may be seen if uptimereaches 104 days or more.
resource.getrusage() andtimes() have a resolution greater orequal to 1 microsecond, and so don’t need a variant with nanosecondresolution.
Note
Internally, Python startsmonotonic() andperf_counter()clocks at zero on some platforms which indirectly reduce theprecision loss.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0564.rst
Last modified:2025-02-01 08:59:27 GMT