Sleep(1) sleep more than 1 millisecond. windows 10 20h2 problem
Asked Answered
H

3

0
clock_t Fps_start_clock, Fps_End_clock;
double Fps_result;
Fps_start_clock = clock();

int ffps = 0;
clock_t updatestartclock, updateendclock;
double updateresult;

while (1)
{
    for (;;)
    {
        Fps_End_clock = clock();
        Fps_result = (double)(Fps_End_clock - Fps_start_clock);
        if (Fps_result < 4)
        {
            Sleep(0);
        }
        else
        {
            Fps_start_clock = Fps_End_clock;
            break;
        }
    }

    /***************************/
    //some code exist...
    ffps++;// this count fps
    /***************************/
    
    

    //these code print fps
    /***************************/
    updateendclock = clock();
    updateresult = (double)(updateendclock - updatestartclock);
    if (updateresult > 1000)
    {
        cout << ffps << endl;
        updatestartclock = updateendclock;
        ffps = 0;
    }
    /***************************/
}

This code run for fps 251hz.

But it has some problem, it use cpu 10%.

If i use Sleep(1) instead of Sleep(0), the result of fps become 64hz on windows 10 20h2.

I think Scheduling problem.

Is there a way to reduce the use of cpu while maintaining 250 Hz?

Heartache answered 2/12, 2020 at 16:36 Comment(3)
If you care about precision at all, do not use Sleep. It is not a "scheduling problem", as you call it, it's a user's understanding problem.Fugere
@Fugere The reason I said this was "scheduled problem" is because the same code using Sleep (1) maintains 250 Hz on Windows 10 1909 and has less than 1 percent cpu usage.Heartache
Related: https://mcmap.net/q/1634469/-sleep-sleeps-too-longHabitual
F
3

A platform agnostic version could be to use the standard <chrono> durations and time points.

Example:

#include <chrono>
#include <thread>

int main() {
    int FPS = 250;
    auto time_between_frames = std::chrono::microseconds(std::chrono::seconds(1)) / FPS;

    auto target_tp = std::chrono::steady_clock::now();

    while(true) {
        target_tp += time_between_frames;          // calculate target point in time
        std::this_thread::sleep_until(target_tp);  // sleep until that time point

        // do stuff
    }
}

As long as do stuff is done in a shorter time than time_between_frames on average this should keep a pretty stable average of 250 FPS.

A low CPU using, but almost busy waiting, version could be to sleep slightly shorter than needed in a loop, hoping that the thread will get scheduled in time.

You can combine it with a threshold value to stop sleeping in the busy loop when you get really close to the target time_point.

    std::chrono::microseconds threshold(10); // some low value (trim 1)
    std::chrono::time_point<std::chrono::steady_clock> now;
    decltype(target_tp - now) sleep_time;

    while(true) {
        target_tp += time_between_frames; // calculate target point in time

        // do stuff

        // an almost busy waiting loop
        while((now = std::chrono::steady_clock::now()) < target_tp) {
            sleep_time = target_tp - now;

            if(sleep_time > threshold) {
                // sleep 2/3 of the time left (trim 2)
                std::this_thread::sleep_for(2 * sleep_time / 3);
            }
        }
    }

The parameters (trim 1 and trim 2) probably need to be calibrated by the program itself at start-up or re-calibrated while running to provide the lowest CPU usage while providing an acceptable FPS stability.

Removing one trim parameter to make calibration simpler would leave this:

    std::chrono::microseconds threshold(20); // some low value

    while(true) {
        target_tp += time_between_frames; // calculate target point in time

        // do stuff

        // sleep short ...
        std::this_thread::sleep_until(target_tp - threshold);

        // busy wait
        while(std::chrono::steady_clock::now() < target_tp) {}
    }

I'm guessing that the threshold would need to be slightly larger and that the CPU usage would be slightly higher in this version - but it would need to be thoroughly tested to say for sure.

On my machine, all three versions puts < 1% load on the processor they run and provide a pretty stable 250 FPS even when do stuff takes 94% of time_between_frames on average with an extreme spread (a random sleep between 0 and 17 * time_between_frames / 9, uniformly distributed).

Farinose answered 2/12, 2020 at 16:57 Comment(2)
I used this code before, but it cause some kind of lag.. It's hard to explain but Sleep(0) is smooth, using this_thread::sleep_until or for whatever cause some lag : (Heartache
@Heartache Oh, that's interesting. I have a very little load on the system (both Linux and Windows) when using it and as long as the average worktime is less than time_between_frames it's also very stable. Did you add a fixed duration to the target time_point?Farinose
P
2

The kernel guarantees that you will Sleep for at least n milliseconds. The Kernel reserves the right to Sleep you as long as it likes. If you need hard realtime, you will need to write a driver (even then you might not get 100% hard guarantees)

Petronille answered 2/12, 2020 at 16:44 Comment(0)
D
1

Use timeBeginPeriod() to change the ticker from 64 hz to up to 1000 hz:

https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod

To run a thread at a steady fixed frequency such as 250 hz, you can use this example 250 hz code, that is accurate and eliminates drift. This thread should be run at the next higher thread priority, so that a Sleep(1) doesn't take more than 2 ms, which is the threshold used for Sleep(1) versus a tight loop on the timer. To further reduce overhead, the 2 ms threshold could be reduced to 1.25 to 1.50 ms.

/* code for a thread to run at fixed frequency */
#include <windows.h>
#pragma comment(lib, "winmm.lib")       /* include winmm.lib */

typedef unsigned long long UI64;        /* unsigned 64 bit int */
#define FREQ    250                     /* frequency */
DWORD    dwLateStep;                    /* late step count */
LARGE_INTEGER liPerfFreq;               /* 64 bit frequency */
LARGE_INTEGER liPerfTemp;               /* used for query */
UI64 uFreq = FREQ;                      /* process frequency */
UI64 uOrig;                             /* original tick */
UI64 uWait;                             /* tick rate / freq */
UI64 uRem = 0;                          /* tick rate % freq */
UI64 uPrev;                             /* previous tick based on original tick */
UI64 uDelta;                            /* current tick - previous */
UI64 u2ms;                              /* 2ms of ticks */
UI64 i;

    /* ... */ /* wait for some event to start thread */
    QueryPerformanceFrequency(&liPerfFreq);
    u2ms = ((UI64)(liPerfFreq.QuadPart)+499) / ((UI64)500);

    timeBeginPeriod(1);                 /* set period to 1ms */
    Sleep(128);                         /* wait for it to stabilize */

    QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp);
    uOrig = uPrev = liPerfTemp.QuadPart;

    for(i = 0; i < (uFreq*30); i++){
        /* update uWait and uRem based on uRem */
        uWait = ((UI64)(liPerfFreq.QuadPart) + uRem) / uFreq;
        uRem  = ((UI64)(liPerfFreq.QuadPart) + uRem) % uFreq;
        /* wait for uWait ticks */
        while(1){
            QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp);
            uDelta = (UI64)(liPerfTemp.QuadPart - uPrev);
            if(uDelta >= uWait)
                break;
            if((uWait - uDelta) > u2ms)
                Sleep(1);
        }
        if(uDelta >= (uWait*2))
            dwLateStep += 1;
        uPrev += uWait;
        /* fixed frequency code goes here */
        /*  along with some type of break when done */
    }

    timeEndPeriod(1);                   /* restore period */
Dostie answered 2/12, 2020 at 16:51 Comment(7)
Obligitory: Never use timeBeingPeriod unless you own the box and everything on it. It affects literally every app running on the box, and possibly increases power consumption. Its a horrendous global hammer that is very rarely a good thing to do.Culpa
That said, it potentially solves ops problem. But if you're writing games then sleeping is pretty much always wrong and so avoiding sleep avoids using this function.Culpa
I add timeBeginPeriod(1); Fps become 64->230 ~ 250. And cpu usage less than 1%. I've solved all the problem I want, but shouldn't I use this function?Heartache
@Dostie Thanks. I didn't meaning that I can't get fixed frequency. Actually my main loop cost very short time, (about 1 - 3 microsecond) So I can just use Constant Frame Rate(CFR). But I google about timeBeginPeriod, some guy post "try not to use it."Heartache
@MikeVine - I tested a program that does a | timeBeginPeriod(1); | Sleep(20*1000); | timeEndPeriod(1) | on a Dell Inspiron 15 7591 2-in-1 laptop (10th gen Core i7-10510U cpu). Running task manager | performance, or performance monitor, I could not see any significant difference in the variable CPU frequency or CPU usage.Dostie
@Heartache - take a look at my prior comment for MikeVine.Dostie
@MikeVine That's no longer the case. timeBeginPeriod is per process since Windows 10 v2004. I'd say, use this method whenever you like! :)Tomahawk

© 2022 - 2024 — McMap. All rights reserved.