Skip to content

gh-88494: Use QueryPerformanceCounter() for time.monotonic() #116781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 14, 2024

Conversation

vstinner
Copy link
Member

@vstinner vstinner commented Mar 14, 2024

On Windows, time.monotonic() now uses the QueryPerformanceCounter() clock to have a resolution better than 1 us, instead of the gGetTickCount64() clock which has a resolution of 15.6 ms.


📚 Documentation preview 📚: https://cpython-previews--116781.org.readthedocs.build/

On Windows, time.monotonic() now uses the QueryPerformanceCounter()
clock to have a resolution better than 1 us, instead of the
gGetTickCount64() clock which has a resolution of 15.6 ms.
@vstinner
Copy link
Member Author

See also #115638 and issue gh-115637.

@vstinner vstinner merged commit 846ad5a into python:main Mar 14, 2024
@vstinner vstinner deleted the QPC branch March 14, 2024 15:42
@vstinner
Copy link
Member Author

vstinner commented Mar 14, 2024

Script to measure the effective clock resolution:

import time

def compute_resolution(func):
    resolution = None
    points = 0
    timeout = time.monotonic() + 1.0
    previous = func()
    while time.monotonic() < timeout or points < 3:
        for loop in range(10):
            t1 = func()
            t2 = func()
            dt = t2 - t1
            if 0 < dt:
                break
        else:
            dt = t2 - previous
            if dt <= 0.0:
                continue
        if resolution is not None:
            resolution = min(resolution, dt)
        else:
            resolution = dt
        points += 1
        previous = func()
    return resolution

def format_duration(dt):
    if dt >= 1e-3:
        return "%.0f ms" % (dt * 1e3)
    if dt >= 1e-6:
        return "%.0f us" % (dt * 1e6)
    else:
        return "%.0f ns" % (dt * 1e9)

def test_clock(name, func):
    print("%s:" % name)
    resolution = compute_resolution(func)
    print("- efffective resolution: %s" % format_duration(resolution))


clocks = ['time', 'perf_counter', 'monotonic']
for name in clocks:
    func = getattr(time, name)
    test_clock("%s()" % name, func)
    info = time.get_clock_info(name)
    print("- implementation: %s" % info.implementation)
    print("- resolution: %s" % format_duration(info.resolution))
    print()

Results before (release build):

time():
- efffective resolution: 2 ms
- implementation: GetSystemTimeAsFileTime()
- resolution: 16 ms

perf_counter():
- efffective resolution: 100 ns
- implementation: QueryPerformanceCounter()
- resolution: 100 ns

monotonic():
- efffective resolution: 15 ms
- implementation: GetTickCount64()
- resolution: 16 ms

Results after (release build):

Running Release|x64 interpreter...
time():
- efffective resolution: 1 ms
- implementation: GetSystemTimeAsFileTime()
- resolution: 16 ms

perf_counter():
- efffective resolution: 100 ns
- implementation: QueryPerformanceCounter()
- resolution: 100 ns

monotonic():
- efffective resolution: 100 ns
- implementation: QueryPerformanceCounter()
- resolution: 100 ns

monotonic() resolution changes from 15.6 ms to 100 ns: it's 156,000x more accurate than before!

vstinner added a commit to vstinner/cpython that referenced this pull request Mar 20, 2024
…ython#116781)

On Windows, time.monotonic() now uses the QueryPerformanceCounter()
clock to have a resolution better than 1 us, instead of the
gGetTickCount64() clock which has a resolution of 15.6 ms.
adorilson pushed a commit to adorilson/cpython that referenced this pull request Mar 25, 2024
…ython#116781)

On Windows, time.monotonic() now uses the QueryPerformanceCounter()
clock to have a resolution better than 1 us, instead of the
gGetTickCount64() clock which has a resolution of 15.6 ms.
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
…ython#116781)

On Windows, time.monotonic() now uses the QueryPerformanceCounter()
clock to have a resolution better than 1 us, instead of the
gGetTickCount64() clock which has a resolution of 15.6 ms.
vstinner added a commit to vstinner/distributed that referenced this pull request May 7, 2024
Closes dask#8641.

_WindowsTime is no longer needed on Windows with Python 3.13. On
Windows, Python 3.13 now uses GetSystemTimePreciseAsFileTime() for
time.time() and QueryPerformanceCounter() for time.monotonic().

* python/cpython#116781
* python/cpython#116822
fjetter pushed a commit to dask/distributed that referenced this pull request May 8, 2024
Closes #8641.

_WindowsTime is no longer needed on Windows with Python 3.13. On
Windows, Python 3.13 now uses GetSystemTimePreciseAsFileTime() for
time.time() and QueryPerformanceCounter() for time.monotonic().

* python/cpython#116781
* python/cpython#116822
@MaxShvets
Copy link

@vstinner could you, please, help me figure out why the script works this way? Specifically, by taking 10 clock readings to measure dt, don't we end up measuring resolution times 10?

@vstinner
Copy link
Member Author

With precise clocks such as QueryPerformanceCounter(), you don't go into the "else" block of the "for". The "else" is only there for low-resolution clocks.

@MaxShvets
Copy link

Got it, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants