Click here to Skip to main content
15,889,351 members
Articles / Desktop Programming / Win32
Article

Better Way to Sleep: Control Execution and Limit CPU Usage by your Threads

Rate me:
Please Sign up or sign in to vote.
4.79/5 (28 votes)
27 Aug 2008CPOL3 min read 104.9K   1.2K   50   27
Here is a class that will help you to control the execution of your threads that involves looping/polling and also limits their CPU usage to a specified limit.

Introduction

I had a code that consists of a thread that was heavily CPU intensive. And I needed to limit its execution so that its CPU usage would not go above a particular limit, irrespective of the hardware/processor over which the program was running.

Moreover we often come across a problem, in which we need to Sleep() a thread for a particular duration of time. Such kind of code often consists of a loop, doing some kind of processing/repetitive polling and sleeping at the end of loop for some time, to save CPU cycles. This goes on and on until the loop is terminated. For example, consider a screen capturing program, which continuously grabs the Desktop Image using BitBlt and send the screen shots to some network socket or queue. In such a program, a thread is dedicated to the task of grabbing the screen and sending it in a loop. Now BitBlt is a very CPU intensive operation, while putting data in a queue is a light weight operation. So this thread would spend most of its time in Bitblt and this will continuously keeps the CPU busy, making other processes starve for CPU.

The Sleep() solution might be ok, but is not the right one, as with Sleep() we cannot control the bandwidth of processing power allocated to that thread. So on high end CPUs, these threads would get very less processing cycles as most of the useful CPU cycles would get wasted while sleeping and on slower processors, these CPU hungry threads would leave little for other processes.

Here is a class that will limit CPU usage of a thread to a specified limit and also help to control its execution. It can be said that it is a better alternative to Sleep().

Using the Code

The core logic of this code is as follows.

The main purpose of our code is to keep the average CPU usage of a thread to a specified limit. This limit is a percentage of sum of user and kernel time spent by the thread to that of sum of idle, user and kernel time spent by the system.

So let's say at time t1 CPU usage by thread is To and by system is So
and let's say at time t2 CPU usage by thread is Tn and by system is Sn
and let's say the ratio/percentage specified is R.

So (Tn-To)/(Sn-So) <= R/100
=> (Tn-To) X 100<=(Sn-So) X R
=> (Tn-To) X 100 - (Sn-So) X R <= 0

So if this equation is greater then zero, then we can say that the limit is crossed.
Moreover let's say we need to sleep for Z time period, so that the average CPU usage percentage falls below the specified limit. So:

(Tn-To)/((Sn-So) + Z) = R/100
=> (Tn-To) X 100 = ((Sn-So) + Z) X R
=> (Tn-To) X 100 = (Sn-So) X R + Z X R 
=> (Tn-To) X 100 - (Sn-So) X R = Z X R 
=> (Tn-To) X (100/R) - (Sn-So) = Z

This shows how we are going to calculate the time period we need to sleep to keep processor usage within limits.

The core class here is CPULimiter. Most of the code is self explanatory. The main function of this class doing all the above given calculations and making the thread sleep is CalculateAndSleep().

To fetch Thread and System CPU usage timing GetThreadTimes and GetSystemTimes Win32 APIs are being used respectively.

Here is the structure of CPULimiter class:

C++
const int DEFAULT_MAX_PERCENTAGE = 20;

/*
CPULimiter:
This class helps to limit the CPU usage/consumption by a thread involving
some kind of repetitive/polling kind of operation in a loop.
The limit can be set by a user through a function of this class.
*/

class CPULimiter
{
    //This integer stores last total system time.
    //total system time is sum of time spent by system 
    //in kernel, user and idle mode
    LARGE_INTEGER m_lastTotalSystemTime;

    //This integer stores last total time spent by this 
    //thread in    kernel space and user space
    LARGE_INTEGER m_lastThreadUsageTime;

    //Variable used to store maximum thread CPU percentage
    //relative to system total time.
    int m_ratio;
public:

    //Constructors
    CPULimiter();
    //Constructor with maximum thread cpu usage percentage
    CPULimiter(int p_ratio);

    //****************Main function.****************** 
    //It evaluates CPU consumption by this thread since 
    //last call to this function, until now. 
    //Internally, it calculates if the thread has exceeded 
    //the maximum CPU usage percentage specified.
    //if yes, it makes the thread sleep for a calculated 
    //time period, to average the total usage to the limit specified.
    //Returns TRUE Successful, else FALSE
    
    BOOL CalculateAndSleep();

    //Inline setter function
    inline void SetRatio(int p_ratio){m_ratio = p_ratio;}
};

And this is the CalculateAndSleep function:

C++
BOOL CPULimiter::CalculateAndSleep()
{
    //Declare variables;
    FILETIME sysidle, kerusage, userusage, threadkern
                   , threaduser, threadcreat, threadexit; 
    LARGE_INTEGER tmpvar, thissystime, thisthreadtime; 

    //Get system kernel, user and idle times
    if(!::GetSystemTimes(&sysidle, &kerusage, &userusage))
        return FALSE;

    //Get Thread user and kernel times
    if(!::GetThreadTimes(GetCurrentThread(), &threadcreat, &threadexit
                            , &threadkern, &threaduser))
        return FALSE;

    //Calculates total system times
    //This is sum of time used by system in kernel, user and idle mode.

    tmpvar.LowPart = sysidle.dwLowDateTime;
    tmpvar.HighPart = sysidle.dwHighDateTime;
    thissystime.QuadPart = tmpvar.QuadPart;

    tmpvar.LowPart = kerusage.dwLowDateTime;
    tmpvar.HighPart = kerusage.dwHighDateTime;
    thissystime.QuadPart = thissystime.QuadPart + tmpvar.QuadPart;

    tmpvar.LowPart = userusage.dwLowDateTime;
    tmpvar.HighPart = userusage.dwHighDateTime;
    thissystime.QuadPart = thissystime.QuadPart + tmpvar.QuadPart;

    //Calculates time spent by this thread in user and kernel mode.

    tmpvar.LowPart = threadkern.dwLowDateTime;
    tmpvar.HighPart = threadkern.dwHighDateTime;
    thisthreadtime.QuadPart = tmpvar.QuadPart;

    tmpvar.LowPart = threaduser.dwLowDateTime;
    tmpvar.HighPart = threaduser.dwHighDateTime;
    thisthreadtime.QuadPart = thisthreadtime.QuadPart + tmpvar.QuadPart;

    //Check if this is first time this function is called
    //if yes, escape rest after copying current system and thread time
    //for further use
    //Also check if the ratio of differences between current and previous times
    //exceeds the specified ratio.
    
    if( thisthreadtime.QuadPart != 0
        && (((thisthreadtime.QuadPart - m_lastThreadUsageTime.QuadPart)*100) 
          - ((thissystime.QuadPart - m_lastTotalSystemTime.QuadPart)*m_ratio)) > 0)
    {
        //Calculate the time interval to sleep for averaging the extra CPU usage 
        //by this thread.

        LARGE_INTEGER timetosleepin100ns;
        timetosleepin100ns.QuadPart = (((thisthreadtime.QuadPart 
                                        - m_lastThreadUsageTime.QuadPart)*100)/m_ratio) 
                       - (thissystime.QuadPart 
                                          - m_lastTotalSystemTime.QuadPart);
        
        //check if time is less than a millisecond, if yes, keep it for next time.
        if((timetosleepin100ns.QuadPart/10000) <= 0)
            return FALSE;
        
        //Time to Sleep :)
        Sleep(timetosleepin100ns.QuadPart/10000);
    }  

    //Copy usage time values for next time calculations.
    m_lastTotalSystemTime.QuadPart = thissystime.QuadPart;
    m_lastThreadUsageTime.QuadPart = thisthreadtime.QuadPart;
    return TRUE;
} 

Here is a sample CPU intensive code using this class:

C++
int _tmain(int argc, _TCHAR* argv[])
{
    std::cout<<"This is a cpu intensive program";
    //define CPULimiter variable
    //and limit cpu usage percentage to 5% here.
    CPULimiter limiter = 5;
    int a,b;
    a = 78;

    //Some CPU intensive code here....
    while(1)
    {    
        b = a;

        //limit cpu usage here.
        limiter.CalculateAndSleep();
    }
    return 0;
}

Note: Here we have confined the usage to 5%. If we hadn't, this would have consumed all the CPU.

C++
CPULimiter limiter = 5; 

History

  • 27th August, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionClass library version Pin
jibrownie18-Jun-12 12:36
jibrownie18-Jun-12 12:36 
GeneralC# Version Pin
dataminers8-Apr-09 1:56
dataminers8-Apr-09 1:56 
GeneralRe: C# Version Pin
dataminers8-Apr-09 2:48
dataminers8-Apr-09 2:48 
GeneralRe: C# Version Pin
hagarwal17-Apr-09 9:01
hagarwal17-Apr-09 9:01 
AnswerRe: C# Version Pin
jdonnelly30-May-11 6:30
jdonnelly30-May-11 6:30 
GeneralRe: C# Version Pin
hagarwal30-May-11 11:48
hagarwal30-May-11 11:48 
GeneralMy vote of 5 Pin
Greg Ellis30-Jan-09 9:21
Greg Ellis30-Jan-09 9:21 
GeneralOh BOYYYY Pin
MrGoodly9-Sep-08 5:29
MrGoodly9-Sep-08 5:29 
GeneralOh Boy Pin
MrGoodly6-Sep-08 6:46
MrGoodly6-Sep-08 6:46 
GeneralYOU KNOW Pin
MrGoodly2-Sep-08 11:00
MrGoodly2-Sep-08 11:00 
GeneralRe: YOU KNOW Pin
hagarwal2-Sep-08 21:46
hagarwal2-Sep-08 21:46 
GeneralRe: YOU KNOW Pin
MrGoodly4-Sep-08 5:27
MrGoodly4-Sep-08 5:27 
AnswerRe: YOU KNOW Pin
hagarwal4-Sep-08 11:20
hagarwal4-Sep-08 11:20 
GeneralOK - But Pin
User 5924129-Aug-08 14:52
User 5924129-Aug-08 14:52 
AnswerRe: OK - But Pin
hagarwal1-Sep-08 6:26
hagarwal1-Sep-08 6:26 
GeneralRe: OK - But Pin
Hawk7772-Sep-08 18:58
Hawk7772-Sep-08 18:58 
GeneralRe: OK - But Pin
User 592417-Sep-08 15:52
User 592417-Sep-08 15:52 
AnswerRe: OK - But Pin
Hawk7777-Sep-08 18:47
Hawk7777-Sep-08 18:47 
"So why would we deliberately throttle a thread (and the CPU)?"

At the CPU level, throttling makes a lot of sense for reducing power consumption and heat output, especially with older CPUs that don't have runtime-changeable clocks. In fact I once kept a CPU working without a fan for a while by force-throttling it to a really low percentage; this was in Linux.

Throttling at the thread level sort of makes sense for similar reasons; for example, you might want to forcefully throttle a thread doing something like SETI-at-home or protein folding so your CPU isn't running at 100% all the time when the system is idle, but allow any other applications you specifically want to use to use as much CPU as they can.

I fully agree that attempting to outguess the scheduler on a matter of correctness by sleeping for tactically interesting periods of time is a completely and utterly disgusting idea and I would never do it.

There are two fairly good cases that webpage missed where Sleep() is useful that I can see right now.

One case is when talking to simple hardware that has timing requirements but doesn't provide any interrupt-oriented interface, such as the parallel port. There are really two subcases here; the first is that you absolutely must wait at least a certain period of time between two actions, and the second is that you will poll the hardware to find out when some action is completed, but don't want to use 100% CPU while polling.

Another case is if you are making some sort of continuously-updated user interface that snapshots some kind of data at some kind of frame rate and displays it. While there are probably better solutions in many cases, one solution would be to have a thread of some sort take a snapshot of the data to display and post a window message to deliver the data, then use Sleep() to wait some period of time before taking another snapshot. Again, this is a tradeoff between update rate and CPU usage, and in many applications using 100% CPU isn't acceptable even if it does lead to the best possible update rate.

Admittedly, only the first subcase of the first case is really a call for Sleep() as such; the other cases would be better framed as a desire for thread throttling, which unfortunately doesn't exist in the Win32 API AFAIK so we have to emulate it.
GeneralRe: OK - But Pin
T-Mac-Oz4-Sep-08 13:53
T-Mac-Oz4-Sep-08 13:53 
GeneralRe: OK - But Pin
User 592417-Sep-08 16:25
User 592417-Sep-08 16:25 
GeneralRe: OK - But Pin
jonas_prag20-Oct-08 2:37
jonas_prag20-Oct-08 2:37 
GeneralRe: OK - But Pin
User 5924120-Apr-09 17:17
User 5924120-Apr-09 17:17 
GeneralWHATEVER Pin
MrGoodly28-Aug-08 10:00
MrGoodly28-Aug-08 10:00 
QuestionRe: WHATEVER Pin
hagarwal29-Aug-08 4:39
hagarwal29-Aug-08 4:39 
GeneralPortable version Pin
berserker_r28-Aug-08 1:34
berserker_r28-Aug-08 1:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.