Подтвердить что ты не робот

Что происходит, когда GetTickCount() обертывается?

Если поток выполняет что-то вроде этого:

const DWORD interval = 20000;
DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    if( GetTickCount() - ticks > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}

В конце концов, тики будут обертываться, когда значение не соответствует DWORD.

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

Кто прав и почему?

Спасибо.

4b9b3361

Ответ 1

От документы:

Истекшее время хранится как DWORD стоимость. Поэтому время будет завершено вокруг нуля, если система запущена непрерывно в течение 49,7 дней. Избегать эту проблему, используйте GetTickCount64. В противном случае проверьте наличие переполнения состояние при сравнении раз.

Однако DWORD не имеет знака - так что с вами все будет в порядке. 0 - "очень большое число" = "небольшое число" (при условии, что у вас нет активной проверки переполнения, конечно). У меня было предыдущее редактирование, в котором говорилось, что вы получите отрицательное число, но это было до того, как я принял во внимание, что DWORD без знака.

У вас все еще будет проблема, если операция займет всего менее 49,7 дней. Это может не быть проблемой для вас;)

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

Ответ 2

Если вы хотите проверить, что происходит, когда GetTickCount() обертывает, вы можете включить тест Application Verifier TimeRollOver.

От Использование Application Verifier в рамках жизненного цикла разработки программного обеспечения:

TimeRollOver заставляет API GetTickCount и TimeGetTime переходить быстрее, чем обычно. Это позволяет приложениям более легко тестировать свою обработку временного опрокидывания.

Ответ 3

Ничего плохого не происходит, пока:

  • Вы вычитаете DWORD s, а не сначала конвертируете в какой-либо другой тип.

  • Ничто, что вы пытаетесь сделать, занимает больше 49,7 дней.

Это связано с тем, что неподписанное арифметическое переполнение хорошо определено в C, а поведение переноса делает именно то, что мы хотим.

DWORD t1, t2;
DWORD difference;

t1 = GetTickCount();
DoSomethingTimeConsuming();
t2 = GetTickCount();

t2 - t1 приведет к правильному значению, даже если GetTickCount обернется. Просто не конвертируйте t2 и t1 в какой-либо другой тип (например, int или double) перед выполнением вычитания.

Это не будет работать, если язык программирования обрабатывает переполнение как ошибку. Он также не будет работать, если DoSomethingTimeConsuming() занимает больше 49,7 дней. Вы не можете сказать, просто просмотрев t2 и t1 сколько раз GetTickCount, к сожалению.


Начнем с обычного случая, когда в игру не попадает:

t1 = 13487231
t2 = 13492843

Здесь t2 - t1 = 5612, что означает, что операция заняла около пяти секунд.

Теперь рассмотрим операцию, которая занимает короткий промежуток времени, но где GetTickCount оборачивается:

t1 = 4294967173
t2 = 1111

Операция заняла 1234мс, но таймер обернулся, а 1111 - 4294967173 - фиктивное значение -4294966062. Что мы будем делать?

Ну, по модулю 2 32 результат вычитания также обертывается:

(DWORD)-4294966062 == (DWORD)1234

Наконец, рассмотрим краевой случай, когда операция занимает почти 2 32 миллисекунды, но не совсем:

t1 = 2339189280
t2 = 2339167207

Здесь GetTickCount завернулся и вернулся туда, где он был.

Теперь t2 - t1 дает значение fog 4294945223. Это потому, что это количество времени, которое фактически выполняла операция!

В общем:

(base + offset) - base ≡ offset mod 2^32

Ответ 4

Я бы предложил рассчитать фактический прошедший период между двумя тиками, не полагаясь на компилятор, чтобы обработать его для вас:

const DWORD interval = 20000;

#define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+1+(cur))

DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    DWORD curticks = GetTickCount();
    if( TICKS_DIFF(ticks, curticks) > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}

Ответ 5

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

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

Ответ 6

Разумеется, вам нужно справиться с этой проблемой.

Ядро Linux обрабатывает проблему с тиковым оберткой с помощью следующего трюка:

#define time_after (a, b) ((long) (b) - (long) (a) < 0))

Идея отличена без знака для подписания и сравнивает их значение, тогда только если | a-b | < 2 ^ 30, то обертка не влияет на результат.

Вы можете попробовать с этим трюком и узнать, почему он работает.

Так как DWORD также является unsigned int, этот трюк также должен работать для окон.

Итак, код может быть таким:

const DWORD interval = 20000;

DWORD ticks = GetTickCount() + интервал;

while (true) {

DoTasksThatTakeVariableTime();

if(time_after(ticks, GetTickCount())
{
    DoIntervalTasks();
    ticks = GetTickCount() + interval;
} 

}

Только если интервал меньше 0x2 ^ 30, он работает.

Ответ 7

Вы можете проверить его;) - У меня здесь есть простое тестовое приложение, которое запустит приложение и подключится к нему GetTickCount(), чтобы вы могли его контролировать из графического интерфейса тестового приложения. Я написал это, так как удаление {t20 > вызовов в некоторых приложениях не так просто.

TickShifter является бесплатным и доступен здесь: http://www.lenholgate.com/blog/2006/04/tickshifter-v02.html

Я написал это, написав серию статей о Test Driven Development, которые использовали некоторый код, который использовал GetTickCount() сломанным способом.

Статьи доступны здесь, если вы заинтересованы: http://www.lenholgate.com/blog/2004/05/practical-testing.html

Однако в итоге ваш код будет работать...

Ответ 8

Происходит большой взрыв. О, извините, большой удар обертывается.

Ответ 9

Эта статья помогла мне, но я думаю, что есть ошибка:

#define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur))

Когда я проверил это на обертке вокруг точки, я обнаружил, что она отключена на 1.

Что для меня работало:

define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur)+1)