題目:寫一個程序,讓用戶來決定Windows任務管理器(Task Manager)的CPU占用率。程序越精簡越好,計算機語言不限。例如,可以實現下面三種情況:
1. CPU的占用率固定在50%,為一條直線;
2. CPU的占用率為一條直線,但是具體占用率由命令行參數決定(參數范圍1~ 100);
3. CPU的占用率狀態是一個正弦曲線。
控制CPU占用率,不僅僅是出于好玩而已。以前的某些程序,特別是某些老游戲,在新的機器上運行速度太快,必須先給CPU降速,才能順利運行那些程序,有個共享軟件CPUKiller,就是專門弄這個的。
控制CPU占用率,因為要調用Windows的API,要考慮到多核、超線程的情況,要考慮到不同版本的Windows的計時相關的API的精度不同,使問題變得相當復雜,若再考慮其它程序的CPU占用率,則該問題則變得很煩人。(taskmgr調用了一個未公開的API)。
對CPU核數的判斷,書上是調用GetProcessorInfo,其實可以直接調用GetSystemInfo,SYSTEM_INFO結構的dwNumberOfProcessors成員就是核數。不知道超線程對這兩種方法有什么影響。
如果不考慮其它程序的CPU占用情況,可以在每個核上開一個線程,運行指定的函數,實現每個核的CPU占用率相同。
要讓CPU的占用率,呈函數 y = calc(t) (0 <= y <= 1, t為時間,單位為ms )分布,只要取間隔很短的一系列點,認為在某個間隔內,y值近似不變。
設間隔值為GAP,顯然在指定t值附近的GAP這段時間內,
CPU占用時間為:busy = GAP * calc(t),
CPU空閑時間為:idle = GAP – busy
因此,很容易寫出下面這個通用函數:
void solve(Func *calc)


{
double tb = 0;

while(1)
{
unsigned ta = get_time();
double r = calc(tb);
if (r < 0 || r > 1) r = 1;
DWORD busy = r * GAP;

while(get_time() - ta < busy)
{}
Sleep(GAP - busy);
tb += GAP;
}
}

如果CPU占用率曲線不是周期性變化,就要對每個t值都要計算一次,否則,可以只計算第一個周期內的各個t值,其它周期的直接取緩存計算結果。
以CPU占用率為正弦曲線為例,顯然:y = 0.5 * (1 + sin(a * t + b))
其周期T = 2 * PI / a (PI = 3.1415927),可以指定T值為60s即60000ms,則
可以確定a值為 2 * PI / T, 若在這60000ms內我們計算200次(c = 200),則GAP值為 T / c = 300ms.也就是說,只要確定了周期和計算次數,其它幾個參數也都確定下來。
完整代碼如下:
#include<windows.h>
#include<cstdio>
#include<cmath>

const int PERIOD = 60 * 1000; //60,000 ms
const int COUNT = 200;

const double PI = 3.1415926535898;
const double GAP = (double)PERIOD / COUNT;
const double FACTOR = 2 * PI / PERIOD;

typedef double Func(double);

inline DWORD get_time()
{ return GetTickCount(); }


double calc2(double x)
{ return (1 + sin(FACTOR * x)) / 2;}

double calc3(double)


{
static double cache[COUNT];
static int count = 0;
static bool first = true;

if (first)
{
double x = 0.0;
for (int i = 0; i < COUNT; ++i, x += GAP)
cache[i] = (1.0 + sin(FACTOR * x)) / 2.0;
first = false;
}
if (count >= COUNT) count = 0;
return cache[count++];
}


double calc4(double)
{ return 0.8;}

void solve(Func *calc)


{
double tb = 0;

while(1)
{
unsigned ta = get_time();
double r = calc(tb);
if (r < 0 || r > 1) r = 1;
DWORD busy = r * GAP;

while(get_time() - ta < busy)
{}
Sleep(GAP - busy);
tb += GAP;
}
}


void run()


{

Func *func[] =
{ calc2, calc3, calc4 };
Func *calc = func[1];
const int MAX_CPUS = 32;
HANDLE handle[MAX_CPUS];
DWORD thread_id[MAX_CPUS];
SYSTEM_INFO info;
GetSystemInfo(&info);
const int num = info.dwNumberOfProcessors;

for (int i = 0; i < num; ++i)
{
if ( (handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,
(VOID*)calc, 0, &thread_id[i])) != NULL)
SetThreadAffinityMask(handle[i], i + 1);
}
WaitForSingleObject(handle[0],INFINITE);
}


int main()


{
run();
}


稍微優化下:
#include<cstdio>
#include<cmath>
#include<windows.h>

const int PERIOD = 60 * 1000; //60,000 ms
const int COUNT = 300;

const double GAP_LINEAR = 100;

const double PI = 3.1415926535898;
const double GAP = (double)PERIOD / COUNT;
const double FACTOR = 2 * PI / PERIOD;


typedef double Func(double);

inline DWORD get_time()
{ return GetTickCount(); }


double calc_sin(double x)
{ return (1 + sin(FACTOR * x)) / 2;}

static double Ratio = 0.7;

void set_ratio()


{
double ret = 0.0;
printf("Ratio:([0,1]) ");
scanf("%lf", &ret);
if (ret < 0.0 || ret > 1.0) ret = 0.5;
Ratio = ret;
}

void solve_nonperiod(Func *calc)


{
double tb = 0;

while(1)
{
unsigned ta = get_time();
double r = calc(tb);
if (r < 0 || r > 1) r = 1;
DWORD busy = r * GAP;

while(get_time() - ta < busy)
{}
Sleep(GAP - busy);
//tb += GAP;
tb += get_time() - ta;
}
}

void solve_period(Func *calc)


{
double x = 0.0;
double cache[COUNT];
for (int i = 0; i < COUNT; ++i, x += GAP)
cache[i] = calc(x);
int count = 0;

while(1)
{
unsigned ta = get_time();
if (count >= COUNT) count = 0;
double r = cache[count++];
DWORD busy = r * GAP;

while(get_time() - ta < busy)
{}
Sleep(GAP - busy);
}
}

void solve_linear(Func*)


{
const unsigned BUSY = Ratio * GAP_LINEAR;
const unsigned IDLE = (1 - Ratio) * GAP_LINEAR;

while(1)
{
unsigned ta = get_time();

while(get_time() - ta < BUSY)
{}
Sleep(IDLE);
}
}


void run(unsigned index = 0, double ratio = -1.0)


{
typedef void Solve(Func *calc);

Func *func[] =
{ calc_sin};
Func *calc = func[0];

Solve *solve_func[] =
{ solve_linear, solve_period, solve_nonperiod };
if (index >= sizeof(solve_func) / sizeof(solve_func[0])) index = 0;
Solve *solve = solve_func[index];

if (solve == solve_linear)
{
if (ratio >= 0 && ratio <= 1) Ratio = ratio;
else set_ratio();
}
const int MAX_CPUS = 32;
HANDLE handle[MAX_CPUS];
DWORD thread_id[MAX_CPUS];
SYSTEM_INFO info;
GetSystemInfo(&info);
const int num = info.dwNumberOfProcessors;

for (int i = 0; i < num; ++i)
{
if ((handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,
(VOID*)calc, 0, &thread_id[i])) != NULL)
SetThreadAffinityMask(handle[i], i + 1);
}
WaitForSingleObject(handle[0],INFINITE);
}


int main()


{
run(0, 0.5);
//run(0);
//run(1);
//run(1);
}
