Что происходит, когда GetTickCount () переносится? - PullRequest
14 голосов
/ 08 апреля 2009

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

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

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

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

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

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

Спасибо.

Ответы [ 9 ]

13 голосов
/ 08 апреля 2009

С документы :

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

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

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

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

10 голосов
/ 10 февраля 2012

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

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

  • Ничто, на что вы пытаетесь рассчитывать, не занимает больше 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 возвращает фиктивное значение 4294945223. Это потому, что на это фактически ушло время!

В целом:

(base + offset) - base ≡ offset mod 2^32
7 голосов
/ 09 апреля 2009

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

С Использование верификатора приложения в жизненном цикле разработки программного обеспечения :

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

4 голосов
/ 04 июня 2009

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

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();
    }
}
2 голосов
/ 08 апреля 2009

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

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

1 голос
/ 19 октября 2009

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

#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)
1 голос
/ 11 июня 2009

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

1 голос
/ 08 апреля 2009

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

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

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

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

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

1 голос
/ 08 апреля 2009

Конечно, вам нужно решить эту проблему.

Ядро Linux решает такую ​​проблему с переносом галочек следующим трюком:

# определить time_after (a, b) ((длинный) (b) - (длинный) (a) <0)) </p>

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

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

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

Так что ваш код может быть чем-то вроде:

const DWORD интервал = 20000;

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

while (true) {

DoTasksThatTakeVariableTime();

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

}

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

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