Search code examples
pythontimesleep

Strange interaction of time.monotonic_ns and time.sleep


I have noticed that there is some strange behaviour when time.monotonic_ns and time.sleep interact with each other in a small time period.

To investigate this I have wrote the following "test".

import time

def main():
    while True:
        start = time.monotonic_ns()
        time.sleep(0.01)  # 10 milliseconds
        end = time.monotonic_ns()
        diff = end - start
        print("diff: {}".format(diff))

if __name__ == "__main__":
    main()

This code most of the times prints a value of about 15000000 (actually exactly 15000000 ns or 16000000 ns) or 15 milliseconds, but sometimes it is just 0. If I decrease the sleep-time more it has the exact same behaviour (i.e. 15 ms or 0 ns).

When I use time.time_ns instead it will be non-zero every time (also more random and it sometimes even goes down to ~11 ms), time.perf_counter_ns also works fine. I am aware that time.process_time will be always 0 because it does not count sleep time. timeit.timit also shows a minimal sleep time of ~8 ms on my system.

I am on a Windows 11 device. According to the python docs for time sleep has a resolution of 100 nanoseconds (which is clearly less than 10 milliseconds). For time.monotonic_ms() I was not able to find a resolution.

I can understand how it could be more than the 10 milliseconds specified but I have no idea how it could be 0 nanoseconds. Python Version: 3.9.13.

I have also tested this on 3.12 with the only difference that IDLE freezes when I try to stop the execution with CTRLC.


Solution

  • I was made aware by a comment to this answer (found by "link hopping" based on a comment here by user "deceze ♦") of a function called time.get_clock_info. This function provides clock-information of serval clocks (in the time-module).

    This is the output for time:

    namespace(implementation='GetSystemTimeAsFileTime()', monotonic=False, adjustable=True, resolution=0.015625)
    

    As it can be seen the resolution is about 16 ms as I have seen before. Apparently monotonic which is the timer for monotonic_ns (in general all ..._ns timers are just specialized variants of the non-ns versions as it seems) has the same resolution (with a different base):

    namespace(implementation='GetTickCount64()', monotonic=True, adjustable=False, resolution=0.015625)
    

    This still does not deliver an answer why it is sometimes zero. perf_counter has a higher precision:

    namespace(implementation='QueryPerformanceCounter()', monotonic=True, adjustable=False, resolution=1e-07)
    

    process_time has the same accuracy as perf_counter:

    namespace(implementation='GetProcessTimes()', monotonic=True, adjustable=False, resolution=1e-07)
    

    Python: 3.9.13 on Windows 11 Home (Edition of Windows seems to make a difference)

    Edit: time.monotonic_ms seems to be only able to output multiples of 16ms. So it either outputs 16 (sometimes it is off by 1 ms, this could be based of a rounding error as the actual resolution isn't 16ms but 15.625ms) or 0 in case of a shorter time period (the same goes for 20ms where it alternates between 15/16 and 31/32 ms).