Функции синхронизации Windows 7 - Как правильно использовать GetSystemTimeAdjustment? - PullRequest
10 голосов
/ 07 октября 2011

Я провел несколько тестов, используя функцию GetSystemTimeAdjustment в Windows 7, и получил несколько интересных результатов, которые я не могу объяснить. Насколько я понимаю, этот метод должен возвращаться, если системное время периодически синхронизируется и, если оно есть, с каким интервалом и с каким шагом оно обновляется ( см. Функцию GetSystemTimeAdjustment в MSDN ).

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

Теперь рассмотрим следующий код тестирования:

#include <windows.h>
#include <iostream>
#include <iomanip>

int main()
{
    FILETIME fileStart;
    GetSystemTimeAsFileTime(&fileStart);
    ULARGE_INTEGER start;
    start.HighPart = fileStart.dwHighDateTime;
    start.LowPart = fileStart.dwLowDateTime;

    for (int i=20; i>0; --i)
    {
        FILETIME timeStamp1;
        ULARGE_INTEGER ts1;

        GetSystemTimeAsFileTime(&timeStamp1);

        ts1.HighPart = timeStamp1.dwHighDateTime;
        ts1.LowPart  = timeStamp1.dwLowDateTime;

        std::cout << "Timestamp: " << std::setprecision(20) << (double)(ts1.QuadPart - start.QuadPart) / 10000000 << std::endl;

    }

    DWORD dwTimeAdjustment = 0, dwTimeIncrement = 0, dwClockTick;
    BOOL fAdjustmentDisabled = TRUE;
    GetSystemTimeAdjustment(&dwTimeAdjustment, &dwTimeIncrement, &fAdjustmentDisabled);

    std::cout << "\nTime Adjustment disabled: " << fAdjustmentDisabled
        << "\nTime Adjustment: " << (double)dwTimeAdjustment/10000000
        << "\nTime Increment: " << (double)dwTimeIncrement/10000000 << std::endl;

}

Он принимает 20 временных меток в цикле и печатает их на консоли. В конце он печатает приращение, с которым обновляются системные часы. Я ожидаю, что разница между временными метками, напечатанными в цикле, будет равна 0 или кратна этому приращению. Тем не менее, я получаю такие результаты:

Timestamp: 0
Timestamp: 0.0025000000000000001
Timestamp: 0.0074999999999999997
Timestamp: 0.01
Timestamp: 0.012500000000000001
Timestamp: 0.014999999999999999
Timestamp: 0.017500000000000002
Timestamp: 0.022499999999999999
Timestamp: 0.025000000000000001
Timestamp: 0.0275
Timestamp: 0.029999999999999999
Timestamp: 0.032500000000000001
Timestamp: 0.035000000000000003
Timestamp: 0.040000000000000001
Timestamp: 0.042500000000000003
Timestamp: 0.044999999999999998
Timestamp: 0.050000000000000003
Timestamp: 0.052499999999999998
Timestamp: 0.055
Timestamp: 0.057500000000000002

Time Adjustment disabled: 0
Time Adjustment: 0.0156001
Time Increment: 0.0156001

Таким образом, похоже, что системное время обновляется с использованием интервала около 0,0025 секунд, а не 0,0156 секунд в качестве возврата на GetSystemTimeAdjustment.

Вопрос второй: В чем причина?

Ответы [ 3 ]

25 голосов
/ 31 июля 2012

API GetSystemTimeAsFileTime обеспечивает доступ к настенным часам системы в формате времени файла.

64-битная структура FILETIME получает системное время как FILETIME в единицах 100 нс, срок действия которых истек с 1 января 1601 года. Для вызова GetSystemTimeAsFileTime обычно требуется от 10 нс до 15 нс.

Чтобы исследовать реальную точность системного времени, предоставляемого этим API, необходимо обсудить детализацию, которая приходит вместе со значениями времени. Другими словами: как часто обновляется системное время? Первая оценка предоставляется скрытым вызовом API:

NTSTATUS NtQueryTimerResolution(OUT PULONG MinimumResolution, 
                                OUT PULONG MaximumResolution, 
                                OUT PULONG ActualResolution);

NtQueryTimerResolution экспортируется собственной библиотекой Windows NT NTDLL.DLL. ActualResolution, сообщаемая этим вызовом, представляет период обновления системного времени в единицах 100 нс, который не обязательно соответствует периоду прерывания. Значение зависит от аппаратной платформы. Общие аппаратные платформы сообщают 156 250 или 100 144 для ActualResolution ; старые платформы могут сообщать о еще больших числах; более новые системы, особенно когда поддерживаются HPET (высокоточный таймер событий) или constant/invariant TSC, могут возвращать 156,001 для ActualResolution .

Это одно из сердцебиений, управляющих системой. MinimumResolution и ActualResolution относятся к конфигурации мультимедийного таймера.

ActualResolution можно установить с помощью вызова API

NTSTATUS NtSetTimerResolution(IN ULONG RequestedResolution,
                              IN BOOLEAN Set,
                              OUT PULONG ActualResolution);

или через интерфейс мультимедийного таймера

MMRESULT timeBeginPeriod(UINT uPeriod);

со значением uPeriod, полученным из диапазона, разрешенного

MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc );

который заполняет структуру

typedef struct {
  UINT wPeriodMin;
  UINT wPeriodMax;
} TIMECAPS;

Типичные значения: 1 мс для wPeriodMin и 1 000 000 мс для wPeriodMax.

При поиске значений min / max здесь есть неудачное неверное толкование:

  • wPeriodMin определяет минимальный период, который понятен в этом контексте.
  • MinimumResolution , возвращаемое NtQueryTimerResolution, с другой стороны, указывает разрешение. Самое низкое достижимое разрешение ( MinimumResolution ) находится в диапазоне примерно до 20 мс, в то время как самое высокое достижимое разрешение ( MaximumResolution ) может составлять 0,5 мс. Тем не менее, 0,5 мс результат не доступен через timeBeginPeriod.

Интерфейс мультимедийного таймера обрабатывает периоды, а NtQueryTimerResolution () - разрешения (обратное значение периода).

Резюме: GetSystemTimeAdjustment - не та функция, на которую нужно смотреть. Эта функция только сообщает, как и если изменения времени сделаны. В зависимости от настройки интерфейса мультимедийного таймера timeBeginPeriod ход времени может выполняться чаще и небольшими порциями. Используйте NtQueryTimerResolution для получения фактического приращения времени. И имейте в виду, что настройка API мультимедийного таймера влияет на значения. (Пример: когда медиаплеер показывает видео, время становится коротким.)

Я диагностировал время окна в значительной степени. Некоторые результаты можно найти здесь .

Примечание: Настройка времени: 0.0156001 четко идентифицирует окна VISTA или выше с HPET и / или constant/invariant TSC в вашей системе.

Реализация: Если вы хотите поймать переход во времени:

FILETIME FileTime,LastFileTime;
long long DueTime,LastTime;
long FileTimeTransitionPeriod; 

GetSystemTimeAsFileTime(&FileTime);
for (int i = 0; i < 20; i++) {
  LastFileTime.dwLowDateTime = FileTime.dwLowDateTime;
  while (FileTime.dwLowDateTime == LastFileTime.dwLowDateTime) GetSystemTimeAsFileTime(&FileTime); 
  // enough to just look at the low part to catch the transition
  CopyMemory(&DueTime,&FileTime,sizeof(FILETIME));
  CopyMemory(&LastTime,&LastFileTime,sizeof(FILETIME));
  FileTimeTransitionPeriod = (long)(DueTime-LastTime);
  fprintf(stdout,"transition period: % 7.4lf ms)\n",(double)(FileTimeTransitionPeriod)/10000);
}   

// WARNING: This code consumes 100% of the cpu for 20 file time increments.
// At the standard file time increment of 15.625 ms this corresponds to 312.5ms!

Но: Когда файловый переход очень короткий (например, установлен timeBeginPeriod(wPeriodMin)), любой вывод, например fprintf или std::cout, может уничтожить результат, потому что задерживает цикл. В таких случаях я бы рекомендовал сохранить 20 результатов в структуре данных и сделать вывод впоследствии.

И: Переход времени файла не всегда может быть одинаковым. Вполне может быть, что приращение времени файла не совпадает с периодом обновления. См. Ссылку выше, чтобы получить более подробную информацию и примеры этого поведения.

Редактировать: Будьте осторожны при вызове timeBeginPeriod, поскольку частые вызовы могут значительно повлиять на системные часы MSDN . Это относится к версии Windows 7.

Звонки на timeBeginPeriod / timeEndPeriod или NtSetTimerResolution могут изменить системное время на целое ActualResolution .Это очень часто приводит к значительным изменениям системного времени.Однако, когда вызовы выполняются во время или около перехода системного времени, отклонения намного меньше.Опрос для перехода / приращения системного времени перед вызовами вышеуказанной функции рекомендуется для требовательных приложений, таких как клиенты NTP.Синхронизация с NTP-сервером затруднена, когда происходят нежелательные скачки в системных временных процессах.

2 голосов
/ 07 октября 2011

Разрешение GetSystemTimeAsFileTime зависит от системы. Если видел, он утверждал, что его между 10 мс и 55 мс. Комментаторы MSDN-документа помещают его в 15 мс и «менее миллисекунды». Что это на самом деле, кажется неясным, но я никогда не видел, чтобы его разрешение утверждалось равным точности 100 нс метки времени.

Это означает, что всегда будет некоторая разница, а также причина, по которой люди используют QueryPerformanceFrequency .

2 голосов
/ 07 октября 2011

Вы фактически определяете, сколько времени занимает один проход через цикл for ().Я получаю немного больше изменчивости, но 5 миллисекунд - это правильно, вывод на консоль не очень быстрый.Произвольно добавьте еще несколько операторов std :: cout, чтобы замедлить его.

...