Почему сон (500) стоит больше, чем 500 мс?


Я Sleep(500) в моем коде и я использовал getTickCount() для проверки времени. Я обнаружил, что он имеет стоимость около 515 МС, более 500. Кто-нибудь знает почему?

6 52

6 ответов:

потому что Win32 API Sleep не является высокоточным сном и имеет максимальную детализацию.

лучший способ получить точный сон-спать немного меньше (~50 мс) и делать занятое ожидание. Чтобы найти точное количество времени, вам нужно busywait, получить разрешение системных часов с помощью timeGetDevCaps и умножьте на 1,5 или 2, чтобы быть в безопасности.

sleep(500) гарантирует сон по крайней мере 500 мс.

но он может спать дольше: верхний предел не определен.

в вашем случае также будут дополнительные накладные расходы при вызове getTickCount().

ваш нестандартный Sleep функция вполне может вести себя по-другому; но я сомневаюсь, что точность гарантирована. Для этого вам понадобится специальное оборудование.

как вы можете прочитать в документация, функция WinAPI GetTickCount()

ограничивается разрешением системного таймера, который обычно находится в диапазоне от 10 мс до 16 мс.

чтобы получить более точное измерение времени, используйте функцию GetSystemDatePreciseAsFileTime

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

В общем случае сон означает, что ваш поток переходит в состояние ожидания, и после 500 мс он будет находиться в состоянии "runnable". Тогда операционной системы планировщик выбирает для выполнения что-то в соответствии с приоритетом и количеством выполняемых процессов в то время. Поэтому, если у вас есть высокоточный сон и высокоточные часы, то это все еще сон для по крайней мере 500 мс, а не точно 500 мс.

как и другие ответы уже отмечали, Sleep() имеет ограниченную точность. Вообще-то,нет реализация a Sleep()подобная функция может быть совершенно точным по нескольким причинам:

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

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

  • вполне возможно, что ОС не может разбудить процесс в требуемое время, например потому что компьютер находится в режиме ожидания. В таком случае, вполне возможно, что ваши 500 мс Sleep() вызов фактически займет несколько часов или дней.

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

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

вот быстрый пример реализации (в псевдокоде), который должен обрабатывать обе эти проблемы:

int interval = 500, giveUpThreshold = 10*interval;
int nextTarget = GetTickCount();

bool active = doAction();
while (active) {
    nextTarget += interval;
    int delta = nextTarget - GetTickCount();
    if (delta > giveUpThreshold || delta < -giveUpThreshold) {
        // either we're hopelessly behind schedule, or something
        // weird happened; either way, give up and reset the target
        nextTarget = GetTickCount();
    } else if (delta > 0) {
        Sleep(delta);
    }
    active = doAction();
}

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

разрешение таймера по умолчанию низкое, при необходимости можно увеличить разрешение по времени. MSDN

#define TARGET_RESOLUTION 1         // 1-millisecond target resolution

TIMECAPS tc;
UINT     wTimerRes;

if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) 
{
    // Error; application can't continue.
}

wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
timeBeginPeriod(wTimerRes);