Отправляет email-рассылки с помощью сервиса Sendsay

Статьи по Visual C++

  Все выпуски  

QueryPerformanceCounter - бомба замедленного действия


Домашняя страница www.devdoc.ru

DevDoc - это новые статьи по программированию каждую неделю.

Заходи и читай!

Домашняя страница Письмо автору Архив рассылки Публикация статьи

Выпуск №63

Здравствуйте уважаемые подписчики, сегодня в номере:

  • Результаты опроса
  • Статья "QueryPerformanceCounter - бомба замедленного действия"

Результаты опроса

Опросы постоянно проводятся на сайте www.devdoc.ru.

Результаты опроса
Для какой платформы вы пишите программы

1Windows
60% ( 359 )
 
2Linux
32% ( 188 )
 
3Mac
3% ( 18 )
 
4Мобильные устройства
5% ( 31 )
 

Всего голосов: 596
Последний голос отдан: Воскресенье - 24 Октября 2010 - 12:59:18


Постоянная ссылка на статью (с картинками): http://www.devdoc.ru/index.php/content/view/queryPerformanceCounter.htm

Автор: Кудинов Александр
Последняя модификация: 2010-10-25 01:56:29

QueryPerformanceCounter - бомба замедленного действия

Все примеры написаны с использованием библиотеки QT

Введение

Однажды мне понадобилось сделать программу, которая работает по собственным часам. Основное требование - часы должны быть нечувствительными к изменению системного времени. Казалось бы, нет ничего проще, однако...

Системные функции, которые непосредственно используются для получения времени/даты привязаны к часам Windows. Сообщение WM_TIMER не гарантирует точности, ядро может пропускать или объединять сообщения, если процессор перегружен.

Тут я вспомнил про чудесные функции QueryPerformanceCounter & QueryPerformanceFrequency. Эта пара считает временные интервалы по тактам процессора.

Требования к моей программе допускали небольшую неточность - часы могли отставать на пару секунд в сутки. А при частоте процессора в пару ГГц не было и речи о том, что часы будут идти неверно. В общем, эта пара API функций с лихвой перекрывала мои потребности. Кроме того, они сами по себе работают очень быстро, и, как утверждает Microsoft - они работают даже на многопроцессорных/многоядерных системах без ошибок. Желающие могут сами посчитать погрешность измерений.

Реализация

Реализация часов очень простая. При старте программы мы получаем "базовое время". В самом простом случае можно просто взять текущее время системы. Я загружал точное время с сервера через сеть. Одновременно с этим мы запоминаем значение счетчика тактов, который получаем вызовом QueryPerformanceCounter.

Когда нам надо считать текущее значение наших часов, мы опять считываем счетчик тактов процессора, переводим его в секунды или миллисекунды и добавляем к базовому времени.

QDateTime m_tmZerroTime = QDateTime::currentDateTime();
 LARGE_INTEGER li;
 QueryPerformanceCounter(&li);
 m_tmQP = li.QuadPart;
 
 :.
 QDateTime tmCurrent = QDateTime::currentDateTime();
 LARGE_INTEGER _TC, _TF;
 QueryPerformanceCounter(&_TC);
 QueryPerformanceFrequency(&_TF);
 QDateTime tmQC = m_tmZerroTime.addMSecs( 1000 * (_TC.QuadPart - m_tmQP) / _TF.QuadPart );
 //tmQC Содержит значение наших часов

Очень просто, не правда ли? Значит продолжим.

Эти часы исправно проработали больше 3-х месяцев в готовой программе, а потом начались необъяснимые глюки. Некоторые пользователи стали жаловаться, что часы в программе отстают от часов в системе. Программа содержала огромное количество бизнес логики и поиски ошибки затянулись. Потом случайно заметили, что наши часы начинают врать на ноутбуках, когда их переводят на питание от батарейки. Тогда закралось подозрение на QueryPerformanceХХХ, хотя горячо любимый Майкрософт в МСДН утверждает, что частота тиков не меняется после старта системы.

Тогда я сделал простое тестовое приложение, которое замеряло разницу между системными часами и моей реализацией. Тестовое приложение проще некуда и там просто нет места ошибкам. Каково же было мое удивление, когда выяснилось, что на моем ноутбуке QueryPerformanceXXXX стало показывать дрейф тактов процессора.

До этого момента я считал, что это самый точный таймер, который есть в Windows. Вы можете скачать тестовое приложение здесь и проверить свою систему.

Ребята из MS не отрицают существование этой проблемы, хотя об этом нет не единого слова в документации на QueryPerformanceCounter & QueryPerformanceFrequency. По их мнению, счетчик тиков съезжает скачками (что видно в тестовом приложении) из-за багов (!?) в некоторых чипсетах. Мое мнение, что счетчик работает неверно, потому что современные процессоры могут менять свою частоту на лету, чтобы экономить энергию. Особенно это актуально для ноутбуков.

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

В моем случае я решил проблему очень просто. Я заменил QueryPerformanceXXXX на GetTickCount(). Последняя функция обеспечивает достаточную точность для моих целей, не подвержена скачкам и не привязана к системным часам. Получилось даже проще, чем было:

QDateTime m_tmZerroTime = QDateTime::currentDateTime();
 DWORD m_tmTC = GetTickCount();
 
 ...
 
 //tmTC содержит значение наших часов.
 QDateTime tmTC = m_tmZerroTime.addMSecs( GetTickCount() - m_tmTC );

PS/ Вместо GetTickCount можно попробовать использовать функцию timeGetTime() - она более точная и на первый взгляд более стабильная.

Copyright (C) Kudinov Alexander, 2006-2010

Перепечатка и использование материалов запрещена без писменного разрешения автора.


В избранное