Должно ли time.perf_counter () быть согласованным между процессами в Python в Windows? - PullRequest
2 голосов
/ 08 июня 2019

Документация для time.perf_counter() указывает, что это для всей системы

время. perf_counter () → float

Возвращает значение (в долях секунды) счетчика производительности, то есть часов с наибольшим доступным разрешением для измерения короткой длительности.Он включает время, прошедшее во время сна, и является общесистемным.Контрольная точка возвращаемого значения не определена, поэтому допустима только разница между результатами последовательных вызовов.

Неправильно ли я интерпретировал для всей системы , чтобы включить согласованностьмежду процессами?

Как показано ниже, он выглядит согласованным в Linux, но не в Windows.Кроме того, поведение Windows с Python 3.6 значительно отличается от 3.7.

Буду признателен, если кто-нибудь сможет указать документацию или отчеты об ошибках, описывающие это поведение.

Контрольный пример

import concurrent.futures
import time

def worker():
    return time.perf_counter()

if __name__ == '__main__':
    pool = concurrent.futures.ProcessPoolExecutor()
    futures = []
    for i in range(3):
        print('Submitting worker {:d} at time.perf_counter() == {:.3f}'.format(i, time.perf_counter()))
        futures.append(pool.submit(worker))
        time.sleep(1)

    for i, f in enumerate(futures):
        print('Worker {:d} started at time.perf_counter() == {:.3f}'.format(i, f.result()))

Результаты в Windows 7

C:\...>Python36\python.exe -VV
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)]

C:\...>Python36\python.exe perf_counter_across_processes.py
Submitting worker 0 at time.perf_counter() == 0.000
Submitting worker 1 at time.perf_counter() == 1.169
Submitting worker 2 at time.perf_counter() == 2.170
Worker 0 started at time.perf_counter() == 0.000
Worker 1 started at time.perf_counter() == 0.533
Worker 2 started at time.perf_counter() == 0.000

C:\...>Python37\python.exe -VV
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]

C:\...>Python37\python.exe perf_counter_across_processes.py
Submitting worker 0 at time.perf_counter() == 0.376
Submitting worker 1 at time.perf_counter() == 1.527
Submitting worker 2 at time.perf_counter() == 2.529
Worker 0 started at time.perf_counter() == 0.380
Worker 1 started at time.perf_counter() == 0.956
Worker 2 started at time.perf_counter() == 1.963

Для краткости я пропустил дополнительные результаты в Windows, но такое же поведение наблюдалось в Windows 8.1.Кроме того, Python 3.6.7 вел себя так же, как 3.6.8, а Python 3.7.1 вел себя так же, как 3.7.3.

Результаты на Ubuntu 18.04.1 LTS

$ python3 -VV
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0]

$ python3 perf_counter_across_processes.py 
Submitting worker 0 at time.perf_counter() == 2075.896
Submitting worker 1 at time.perf_counter() == 2076.900
Submitting worker 2 at time.perf_counter() == 2077.903
Worker 0 started at time.perf_counter() == 2075.900
Worker 1 started at time.perf_counter() == 2076.902
Worker 2 started at time.perf_counter() == 2077.905

$ python3.7 -VV
Python 3.7.1 (default, Oct 22 2018, 11:21:55) 
[GCC 8.2.0]

$ python3.7 perf_counter_across_processes.py 
Submitting worker 0 at time.perf_counter() == 1692.514
Submitting worker 1 at time.perf_counter() == 1693.518
Submitting worker 2 at time.perf_counter() == 1694.520
Worker 0 started at time.perf_counter() == 1692.517
Worker 1 started at time.perf_counter() == 1693.519
Worker 2 started at time.perf_counter() == 1694.522

1 Ответ

1 голос
/ 08 июня 2019

В Windows time.perf_counter основан на WINAPI QueryPerformanceCounter.Этот счетчик общесистемный.Для получения дополнительной информации см. получение меток времени с высоким разрешением .

Тем не менее, perf_counter в Windows возвращает значение, относящееся к значению запуска процесса.Таким образом, это не общесистемная ценность.Это делается для того, чтобы уменьшить потерю точности при преобразовании целочисленного значения в float, которое имеет только 15 десятичных цифр точности.Использование относительного значения в большинстве случаев не вызывается, для которого требуется только микросекундная точность.Должен быть необязательный параметр для запроса истинного значения счетчика QPC, особенно для perf_counter_ns в 3.7 +.

Что касается различных начальных значений, возвращаемых perf_counter в 3.6 против 3.7, реализация немного измениласьчерез некоторое время.В 3.6.8 perf_counter реализован в Modules / timemodule.c , поэтому начальное значение сохраняется, когда модуль time впервые импортируется и инициализируется, поэтому вы видите первый результат как0,000 секундВ более поздних выпусках это реализовано отдельно в C API Python.Например, см. "Python / pytime.c" в последней бета-версии 3.8.В этом случае ко времени, когда код Python вызывает time.perf_counter(), счетчик значительно увеличился после значения запуска.

Вот альтернативная реализация, основанная на ctypes, которая использует общесистемное значение QPC вместо относительного значения..

import sys

if sys.platform != 'win32':
    from time import perf_counter
    try:
        from time import perf_counter_ns
    except ImportError:
        def perf_counter_ns():
            """perf_counter_ns() -> int

            Performance counter for benchmarking as nanoseconds.
            """
            return int(perf_counter() * 10**9)
else:
    import ctypes
    from ctypes import wintypes

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

    kernel32.QueryPerformanceFrequency.argtypes = (
        wintypes.PLARGE_INTEGER,) # lpFrequency

    kernel32.QueryPerformanceCounter.argtypes = (
        wintypes.PLARGE_INTEGER,) # lpPerformanceCount

    _qpc_frequency = wintypes.LARGE_INTEGER()
    if not kernel32.QueryPerformanceFrequency(ctypes.byref(_qpc_frequency)):
        raise ctypes.WinError(ctypes.get_last_error())
    _qpc_frequency = _qpc_frequency.value

    def perf_counter_ns():
        """perf_counter_ns() -> int

        Performance counter for benchmarking as nanoseconds.
        """
        count = wintypes.LARGE_INTEGER()
        if not kernel32.QueryPerformanceCounter(ctypes.byref(count)):
            raise ctypes.WinError(ctypes.get_last_error())
        return (count.value * 10**9) // _qpc_frequency

    def perf_counter():
        """perf_counter() -> float

        Performance counter for benchmarking.
        """
        count = wintypes.LARGE_INTEGER()
        if not kernel32.QueryPerformanceCounter(ctypes.byref(count)):
            raise ctypes.WinError(ctypes.get_last_error())
        return count.value / _qpc_frequency

QPC обычно имеет разрешение 0,1 микросекунды.A float в CPython имеет 15 десятичных цифр точности.Таким образом, эта реализация perf_counter находится в пределах разрешения QPC на срок службы около 3 лет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...