分享

让cpu占用率曲线听你指挥学习笔记

 双木亭 2012-07-10
1.写一个程序,让用户决定windows任务管理器(Task Manager)的CPU占用率。程序越精简越好,机器语言不限。例如:可实现一下三种情况:
(1)CPU的占用率固定在50%,为一条直线;
(2)cpu的占用率为一条直线,具体占用率由命令行参数决定(参数范围为1-100);
(3)CPU的占用率状态是一条正弦曲线。
2分析与解法:让我们仔细思考写程序时曾经遇到的问题,如果不小心写了一个死循环,CPU占用率会跳到最高,并且一直保持在100%,我们也可以打开任务管理器,实际观测一下它是怎么样变动的,凭肉眼观察,它大约是1S重更新一次,一般情况下,CPU使用率会很低,但是,当用户运行一个程序,执行一些复杂的操作的时候,CPU的使用率就会急剧升高,当用户晃动鼠标时,CPU的使用率也有小幅度的变化。那当任务管理器报告CPU的使用率为0的时候,谁使用CPU呢,通过任务管理器的进程(PROCESS)一栏可以看到,system idle process占用CPU空闲的时间,这个学习过操作系统的娃肯定熟悉不过了哈。系统那么多进程,他们什么时候能够闲下来,答案很简单,这些程序或者在等待用户的输入,或者在等待某些事情的发生,或者主动进入休眠状态咯。在任务管理器的一个刷新周期内,CPU忙(执行应用程序)的时间和刷新周期时间的比率就是CPU的占用率,也就是说,任务管理器中显示的是每个刷新周期内的CPU占用率的统计平均值。因此我们可以写一个程序,让它在任务管理器的刷新期间内一会儿忙,一会儿闲。然后调节忙/闲的比例,就可以控制任务管理器中显示CPU占用率。
(1)解法1
要操控CPU的使用率曲线,就需要使CPU在一段时间内(根据task manager的采样率跑BUSY与IDLE两个不同的循环(loop)),从而通过不同的时间比例,来调节CPU使用率。
BUSY LOOP可以通过空循环来实现,idle可以通过sleep()来实现
问题的关键在于如何控制两个loop的时间,我们先测试sleep一段时间,然后循环N次,估算N的值。那么对于一个空循环for(int i=0;i<n;i++);又该如何估算这个最合适的N值呢,我们知道cpu执行的是机器指令,而最接近机器指令的语言是汇编语言,所以我们可以先把这个空循环简答的写成如下汇编代码后再进行分析:
loop:
mov dx,i
inc dx
mov i,dx
cmp i,n
jl loop           ;i<n时重复循环
我的CPU是1.28GHZ(1.28*109次个时钟周期每秒)。现在COU每个时钟周期可以执行两条以上的代码,我们取平均值2条,于是有(1280000000*2)/5=512000000(循环每秒),也就是说CPU一秒钟可以运行这个空循环512000000次,但是我们还是不能够将N=512000000,然后sleep(1000)了事,如果我们把CPU工作1s,然后休息1s钟,波形很有可能是锯齿型的,先达到一个峰值,然后跌倒一个很低的占用率。
我们尝试降低两个数量级,令N=51200000,而睡眠时间则相应的改为10ms  sleep(10),用10ms的原因是因为比较接近windows的调度时间片,如果选的太小,比如1ms,就会造成线程频繁的被唤醒和挂起,无形中又增加了内核时间的不确定性。本人试过,在N=512000000的时候cpu占用率=100%
n=51200000时候cpu占用率≈95%,n=5120000时候,cpu占用率≈50%
代码清单如下:
#include "Windows.h"
#include "stdlib.h"
#include "math.h"
int main()
{
    for(; ;)
    {
        for(int i=0;i<5120000;i++)
            ;
        Sleep(10);
    }
    return 0;
}


只是约等于哈,因为 我电脑还有点其它东西,做这个程序的时候最好把其它程序乱七八糟的全部关了,才能得到最好的数据,之后就是一直停在50%。
在不断调整5120000的参数后,我们就可以在一台指定的机器上获得一条大致稳定的50%CPU占用率曲线。使用这种方法要注意两点影响:
A:尽量减少sleep/awake的频率,以减少操作系统内核调度程序的干扰;
B:尽量不要调用system call(比如I/O这些privilege instruction),因为她会导致很多不可控的内核运行时间。
该方法的缺点也很明显:不能适应机器差异性。一旦换了一个CPU我们又得重新估算N值。
解法2:使用GetTickC
我们知道GetTIckCount()可以得到系统启动到现在所经历时间的毫秒值,最多能统计到49.7天。我们可以利用GetTickCount()来判断busy loop 要循环多久
代码如下所示
#include "Windows.h"
#include "stdlib.h"
#include "math.h"
#define ture 1
int main()
{
    int  busyTime=10;
    int idleTime=busyTime;   //same ratio will lead to 50%cpu usage
    _int64 startTime=0;
    while(ture)
    {
         startTime=GetTickCount();
         while((GetTickCount() - startTime)<=busyTime)
                    ;//idle loop
         Sleep(idleTime);
    
    }
    return 0;
}

这两种解法都是假设目前系统上只有当前程序在运行,但是实际上操作系统中有很多很多的程序会同时执行各种各样的任务吗,如果此时其它进程使用了
10%,那么这个程序只能使用40的CPU,这样才能达到50%的效果----此时需要一个工具,Perfmon.exeperfmon是从windows NT开始就包含在windows管
理工具组中的专业检测工具之意,perfmon可获取有关操作系统,应用程序以及硬件的各种效能计数器(perf counter)Perfmon的用法相当直接,只要选
择你索要检测的对象,(比如:处理器、ram或者硬盘),或者选择效能计数器(比如监视物理磁盘的平均队列长度)即可。我们可以通过写程序来查询
Perfmon的值,Microsoft .net framework提供了performanceCounter这一对象,可以方便的得到当前各种性能数据,包括CPU的使用率,代码清单如下:
// c# code
static void MakeUsage(float level)
{
       PerfomanceCountwe p=new PerformanceCounter("Processor","%Processor Time","Total");
        if(p==NULL)
     {
        return
      }
     while(ture)
    {
         if (p.NextValue()>level)
         System.Threading.Thread.Sleep(10);
    }
}
这个似乎是一段c#代码,没学过,也不晓得该带什么头文件,因此没有实验过
好吧,现在可以画一段美好的正弦曲线了:
#include "Windows.h"
#include "stdlib.h"
#include "math.h"
#include <stdio.h>
#include <tchar.h>
const double SPLIT=0.01;
const int COUNT =200;
const double PI=3.14159265;
const int INTERVAL=300;
#define ture 1
int _tmain(int argc,TCHAR* argv[])
{
    DWORD busySpan[COUNT];
    DWORD idleSpan[COUNT];
    int half=INTERVAL/2;
    double radian=0.0;
    for(int i=0;i<COUNT;i++)
    {
        busySpan[i]=(DWORD)(half + (sin(PI*radian)*half));
        idleSpan[i]=INTERVAL-busySpan[i];
        radian+=SPLIT;
    }
    DWORD startTime=0;
    int j=0;
    while(ture)
    {
      j=j%COUNT;
      startTime=GetTickCount();
      while((GetTickCount()-startTime)<=busySpan[j])
          ;
      Sleep(idleSpan[j]);
      j++;
    }
    return 0;
}
用过C的人都知道每一个C的程序都会有一个main(),但有时看别人写的程序发现主函数不是int main(),而是int _tmain(),而且头文件
也不是<iostream.h>而是<stdafx.h>,会困惑吧? 一起来看看他们有什么关系吧 首先,这个_tmain()是为了支持unicode所使用的main一个别名而已,既然是别名,应该有宏定义过的,在哪里定义的呢?就在那个让你
困惑的<stdafx.h>里,有这么两行 #include <stdio.h> #include <tchar.h> 我们可以在头文件<tchar.h>里找到_tmain的宏定义 #define _tmain main 所以,经过预编译以后, _tmain就变成main了,这下明白了吧(我的编译器里找不到<stdafx.h>所以我直接#include <stdio.h>
#include <tchar.h>这两个东西进去,编译成功,搞定。 )

这个就是编译出来的东西

讨论问题:
假如机器是多个cpu像现在很多电脑都是双核神马的,如何才能在多cpu时显示同样的状态,例如在双核的机器上,如果让一个
单线程的程序死循环,能让两个cpu的使用率达到50%的水平吗?为什么?。。。。。我也不知道,我的电脑是单核的
多cpu的问题首先需要获得系统的CPU的信息,可以使用GetProcessInfo()获得多处理器的信息,然后指定进程在那一个处理
器上运行,其中制定运行的是SetThreadAffintyMask()函数。
另外,还可以使用RDTSC指令获得CPU核心运行周期数。
在x86上定义函数:
inline _int64 GetCPUTickCount()
{
_asm
{
rdtsc;
}
}
在x86平台上定义:#define GetCPUTickCount() _rdtsc() 使用CalNtPowerInformation API得到cpu的频率,从而
将周期数转化为毫秒数,如下面所示:
_PROCESSOR_POWER+INFORMATION info;
CallNTPowerInformation(11,
NULL,
&info,
Sizeof(info));
_int64 t_begin =GetCPUTickCount();
_int64 t_end =GetCPUTickCount();
double millsec=((double)t_end-(double)t_begin)/(double)info.CurrentMhz;
RDTSC指令读取当前CPU的周期数,在多CPU系统中这个周期数在不同的CPU之间的基数不同,频率可能也不同,用从两个
不同的CPU得到的周期数来计算会得到没用意义的值。如果线程在运行在被调度到了不同的CPU就会出现上述的情况,可
用SetThreadAffinityMask避免线程迁移。另外cpu的频率会随系统供电以及负荷有所调整。
总结:线程/进程/系统效能的API大致有以下这些:
1.Sleep()---这个方法能够让当前线程“停”下来
2.WaitForSingleObject()--自己停下来等待某个事件的发生
3.QueryPerformanceFrequency(),QueryPerformanceCounter()--让你访问到精度更高的CPU数据
4.GetTickCount()----dida
5.timeGetSystemTime()--另一个得到高精度时间的方法
6.PerformanceCounter---效能计数器
7.GetProcessorInfo()/SetThreadAffinityMask()遇到多核问题怎么办呢,这两个帮你更好的控制CPU
8.GetCPUTickCount()想拿到COU核心运行周期数吗,用这个方法
好了,这个实验over了,祝大家学习愉快








    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多