Click here to Skip to main content
Click here to Skip to main content

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

By , 27 Aug 2008
 

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:

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:

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:

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.

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)

About the Author

Lone Developer
Software Developer (Senior)
India India
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionClass library versionmemberjibrownie18 Jun '12 - 12:36 
I was wondering if you could post a class library version of your code. Ive tried converting it but come up with a series of error for some reason.
GeneralC# Versionmemberdataminers8 Apr '09 - 1:56 
Could you write C# version of this program?
 
Best Regards...
GeneralRe: C# Versionmemberdataminers8 Apr '09 - 2:48 
Or do you create DLL using for .NET application?
GeneralRe: C# VersionmemberLone Developer17 Apr '09 - 9:01 
Sure, i definitely wrap this call in a InProc COM dll to be used anywhere.
Thanks for appreciation. Smile | :)
AnswerRe: C# Versionmemberjdonnelly30 May '11 - 6:30 
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetSystemTimes(
out ComTypes.FILETIME lpIdleTime,
out ComTypes.FILETIME lpKernelTime,
out ComTypes.FILETIME lpUserTime
);
 
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetThreadTimes(
IntPtr handle,
out ComTypes.FILETIME creation,
out ComTypes.FILETIME exit,
out ComTypes.FILETIME kernel,
out ComTypes.FILETIME user);
 
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetCurrentThread();
 
[StructLayout(LayoutKind.Explicit, Size = 8)]
struct LARGE_INTEGER
{
[FieldOffset(0)]
public Int64 QuadPart;
[FieldOffset(0)]
public UInt32 LowPart;
[FieldOffset(4)]
public Int32 HighPart;
}
 
// Last total system time and is the sum of time spent by system
// in kernel, user and idle mode
private LARGE_INTEGER m_lastTotalSystemTime;
 
// Last total time spent by this thread in kernel space and user space
private LARGE_INTEGER m_lastThreadUsageTime;
 
private string m_lastThreadName;
 
// Maximum thread CPU percentage relative to system total time.
private int m_maxCPU;
 
public CPUGovernor(int maxCPU)
{
m_maxCPU = maxCPU;
}
 
private readonly object _lock = new object();
 
/// <summary>
///
/// </summary>
/// <returns></returns>
public bool Sleep()
{
ComTypes.FILETIME sysidle ;
ComTypes.FILETIME kerusage ;
ComTypes.FILETIME userusage ;
ComTypes.FILETIME threadkern ;
ComTypes.FILETIME threaduser ;
ComTypes.FILETIME threadcreat ;
ComTypes.FILETIME threadexit;
 
LARGE_INTEGER tmpvar = new LARGE_INTEGER();
LARGE_INTEGER thissystime = new LARGE_INTEGER();
LARGE_INTEGER thisthreadtime = new LARGE_INTEGER();

//Get system kernel, user and idle times
if (!GetSystemTimes(out sysidle, out kerusage, out userusage))
return false;

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

//Calculates total system times
//This is sum of time used by system in kernel, user and idle mode.
 
tmpvar.LowPart = (UInt32)sysidle.dwLowDateTime;
tmpvar.HighPart = sysidle.dwHighDateTime;
thissystime.QuadPart = tmpvar.QuadPart;
 
tmpvar.LowPart = (UInt32)kerusage.dwLowDateTime;
tmpvar.HighPart = kerusage.dwHighDateTime;
thissystime.QuadPart = thissystime.QuadPart + tmpvar.QuadPart;
 
tmpvar.LowPart = (UInt32)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 = (UInt32)threadkern.dwLowDateTime;
tmpvar.HighPart = threadkern.dwHighDateTime;
thisthreadtime.QuadPart = tmpvar.QuadPart;
 
tmpvar.LowPart = (UInt32)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 maxCPU of differences between current and previous times
//exceeds the specified maxCPU.
if (thisthreadtime.QuadPart != 0 && (((thisthreadtime.QuadPart - m_lastThreadUsageTime.QuadPart) * 100) - ((thissystime.QuadPart - m_lastTotalSystemTime.QuadPart) * m_maxCPU)) > 0)
{
//Calculate the time interval to sleep for averaging the extra CPU usage
//by this thread.
 
LARGE_INTEGER timetosleepin100ns = new LARGE_INTEGER();
timetosleepin100ns.QuadPart = (((thisthreadtime.QuadPart - m_lastThreadUsageTime.QuadPart) * 100) / m_maxCPU) - (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 Smile | :)
Thread.Sleep((int)timetosleepin100ns.QuadPart / 10000);
}
 
//Copy usage time values for next time calculations.
m_lastTotalSystemTime.QuadPart = thissystime.QuadPart;
m_lastThreadUsageTime.QuadPart = thisthreadtime.QuadPart;
m_lastThreadName = Thread.CurrentThread.Name;
 
return true;
}
}
GeneralRe: C# VersionmemberLone Developer30 May '11 - 11:48 
Hi
 
Thank you posting the C# version.
It should be very useful.
 
Regards
 
Himanshu Agarwal
GeneralMy vote of 5memberGreg Ellis30 Jan '09 - 9:21 
This is great code. Thread priorities are not enough. They do not give you enough control, this class makes life much easier. Thank you.
GeneralOh BOYYYYmemberMrGoodly9 Sep '08 - 5:29 
Okay, i think the "Lone Developer" is just stupid. That is why he is a "Lone" developer, cause noone wants to be associated with him or her or it. Smile | :)
GeneralOh BoymemberMrGoodly6 Sep '08 - 6:46 
Miss Lone Developer;
 
You actually took the time to write so much crap. Wow...dont you have a life? While you are writting all this crap, your wife is sleeping with your sister. And your sister is blowing your father. I can not come to your place, you live in Punjab, i live in NYC.
 
I suspect you are a transsexual.
GeneralYOU KNOWmemberMrGoodly2 Sep '08 - 11:00 
I mean you are a PAN SHOD and a MA SHOD
GeneralRe: YOU KNOWmemberLone Developer2 Sep '08 - 21:46 
See my reply to this in your email.
GeneralRe: YOU KNOWmemberMrGoodly4 Sep '08 - 5:27 
I DONT CHECK THAT EMAIL ADDRESS....BUT ALWAYS REMEMBER, YOU ARE A MA SHOD AND A PAN SHOD
AnswerRe: YOU KNOWmemberLone Developer4 Sep '08 - 11:20 
So as you wish. Following was my reply for you (and other people like you).
 
-------------------------------------------------------------------------
 
In my previous message also, i asked you the meanings of these words.
 
I don't know what you mean by these words "Pan shode" and "Ma shod", but what appears from your language these words doesn't have good meanings attached to them.
If no then explain there meaning to me (i only knew these are Irani words as i found in Google).
If yes then i would simply say:
 
YOU IMMEDIATELY NEED PSYCHIATRIC HELP
 
Because i see no reason you saying all these to me, this is a technical forum, and i don't even know a single bit about you as a person (before this incidence, but now i have came to know a little bit about your psychic nature from your these repeated messages). So to me it appears you must have some serious problem with your mind and a insane has got access to internet.
 
And if you knew me somehow and are serious about something then lets discuss it offline, mail me your issue at agarwal.himanshu502@gmail.com or come to my place, I'll assure you, you won't have any issue left after you meet me.
 
----------------------------------------------------------------------------
 
You are in fact not commenting any thing on me, but telling about yourself to the whole world out here and you are eager to show more about yourself. As you wish. Smile | :)
 
Your words are just like a dog's bark to me, which is just to be ignored, as one passes by.
 
SO KEEP BARKING Smile | :)
GeneralOK - Butmemberpwasser29 Aug '08 - 14:52 
In your intro you say.
"The Sleep() solution might be ok, but is not the right one, as with Sleep() we can not control the bandwidth of processing power allocated to that thread."
then you go ahead and use Sleep() to solve your problem.
 
This soloution may OK for a few threads but:
 
1. What about letting the OS do something using thread priorities?
2. When the calculated Sleep() is less than 1ms you are still in a polling loop.
3. You make no mention of the granularity of Sleep() in relation to timer ticks.
 
Peter Wasser

AnswerRe: OK - ButmemberLone Developer1 Sep '08 - 6:26 
What about letting the OS do something using thread priorities?
 
Thread priorities gives you a very limited control over how much CPU cycles a particular thread gets in a time frame. And then you don't have fine control over priorities too as there are only definite priority levels and not custom ones. Moreover with thread priorities you just tell the os the importance of a particular thread to execute. But even then a thread is CPU hungry, you will notice, even after assigning it the lowest priority, it consumes almost all the CPU. (And i don't have any answer for this, practically this shouldn't happen).
 
When the calculated Sleep() is less than 1ms you are still in a polling loop.
 
This case is handled specially in the code. It might be the case that the calculated time period to Sleep() comes out to be lesser then a millisecond. In this case previous values are not modified so that this particular time chunk comes into account in next calculation, to avoid slippage.
 
You make no mention of the granularity of Sleep() in relation to timer ticks.
 
This is something i never notice before and i miss to take care in this code. Thanks you Smile | :) I'll update the code with respect to this information soon.
GeneralRe: OK - ButmemberHawk7772 Sep '08 - 18:58 
"Moreover with thread priorities you just tell the os the importance of a particular thread to execute. But even then a thread is CPU hungry, you will notice, even after assigning it the lowest priority, it consumes almost all the CPU. (And i don't have any answer for this, practically this shouldn't happen)."
 
Of COURSE it should! Thread priorities are just that: PRIORITIES. They rank threads' importances RELATIVE TO EACH OTHER. If only one thread actually wants the CPU at a time, it doesn't matter how unimportant it is, it should get 100% of the time. That's the point of thread priorities. They only matter if two threads are asking for the CPU simultaneously, in which case the more important thread gets more time.
 
What you have done in your code is throttling, which is quite different to prioritization (and is useful in its own way).
GeneralRe: OK - Butmemberpwasser7 Sep '08 - 15:52 
Hawk777 wrote:
What you have done in your code is throttling, which is quite different to prioritization (and is useful in its own way).

 

Absolutely correct.
 
So why would we deliberately throttle a thread (and the CPU)?
 
In my view using anything but Sleep(0) to assist thread scheduling is bad practice. Sleep(nnn) cannot extract something from the CPU that is not there.
 
This sums it up:
 
http://www.flounder.com/badprogram.htm#Sleep
 
Using program design and thread priority settings are the correct (and maybe only) ways. If that does not work then the CPU is overloaded - get a faster CPU.
 
http://msdn.microsoft.com/en-us/library/ms686277(VS.85).aspx
 
There are some comments I could make about the code itself but I would rather address the basic approach offered in this article.
 
Peter Wasser

AnswerRe: OK - ButmemberHawk7777 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 - ButmemberT-Mac-Oz4 Sep '08 - 13:53 
Lone Developer wrote:
Thread priorities gives you a very limited control over how much CPU cycles a particular thread gets in a time frame.

 
"Limited" is absolutely the right word! Window's thread prioritisation is a total POS - in my experience, a difference in thread priority of only 1 and you will be lucky to get an execution ratio of 10:1.
 
Lone Developer wrote:
You make no mention of the granularity of Sleep() in relation to timer ticks.
This is something i never notice before and i miss to take care in this code.

 
Consider the MSDN documentation on Sleep[^] (bold added by me):
This function causes a thread to relinquish the remainder of its time slice and become unrunnable for at least the specified number of milliseconds, after which the thread is ready to run.
 
For legacy reasons the Sleep(...) timer resolution is 55ms so calling Sleep(1) at the moment a thread is resumed will actually yield for 55ms not 1 as is the intent.
 
Even worse, Sleep(0) does not yield to threads of lower priority! The newer API function SwitchToThread[^] does but it does not include any timing mechanism.
 
For a more robust and tunable solution you might consider using events (that you never actually set) and wait functions with timeouts.
CreateEvent[^]
WaitForSingleObject[^]
or in a UI thread:
MsgWaitForMultipleObjects[^]
 
T-Mac-Oz

GeneralRe: OK - Butmemberpwasser7 Sep '08 - 16:25 
T-Mac-Oz wrote:
Even worse, Sleep(0) does not yield to threads of lower priority! The newer API function SwitchToThread[^] does but it does not include any timing mechanism.

 
Thanks for this.
 
Peter Wasser

GeneralRe: OK - Butmemberjonas_prag20 Oct '08 - 2:37 
This was true sometimes in the good old days of Win98 .. but since {at least} Win2000 timer resolution 55 ms is gone forever .. luckily for us.
GeneralRe: OK - Butmemberpwasser20 Apr '09 - 17:17 
Not strictly true - check timeGetDevCaps.
 
Sleep was never intended as a timing mechanism.
 
Peter Wasser

GeneralWHATEVERmemberMrGoodly28 Aug '08 - 10:00 
You are a Pan Shode
QuestionRe: WHATEVERmemberLone Developer29 Aug '08 - 4:39 
Sorry, I didn't get you. What do you mean by "Pan Shode"?
GeneralPortable versionmemberberserker_r28 Aug '08 - 1:34 
Good job Smile | :)
What about a portable version[^]?

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 27 Aug 2008
Article Copyright 2008 by Lone Developer
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid