• 让CPU的占有率曲线听我指挥


    最近我要在公司的一个study group负责AWS的AutoScaling功能的介绍。AWS可以根据instance(虚拟机)的CPU使用量进行scaling。

    为了做demo,于是就有这样一个需求:让instance上的CPU听我指挥,当然简单的方法就是写一个死循环,让CPU 100%。但如果make things more interesting,希望实现CPU在某个范围内变化又要怎么做哩?

    之前看过,邹欣大神的书《编程之美》,其中第一个问题就是“让CPU占有率曲线听你指挥”,里面提到了一些解法,更有甚者,做到了能让CPU占有率曲线按正弦函数波动。我和同事大神Jason san中午吃饭时聊了这个需求,想不到他下午就分别用C++和Python实现了一种动态适应的解决方法。以下我们就来讨论下这个有趣问题的解法:

    首先书上提到一个简单方法让CPU维持在50%:

    让CPU在一段时间内(根据Task Manager的采样率)跑busy和idle两个不同的循环,从而通过不同的时间比例,来调节CPU使用率。

    那么对于一个空循环

    for(i = 0; i < n; i++);

    又该如何来估算这个最合适的n的值呢?首先我们把这个空循环简单写成汇编代码(伪代码):

    1 next:
    2 mov eax, dword ptr[i]; i放入寄存器
    3 add exa, 1; 寄存器加1
    4 mov dword ptr [i], eax; 寄存器赋回i
    5 cmp eax, dword ptr[n]; 比较i和n
    6 jl next; i小于n时重复循环

    假设这段代码要运行的CPU是P4,主频是2.4Ghz(2.4*10的9次方个时钟周期每秒)。现代CPU每个时钟周期可以执行两条以上的代码,我们取平均值两条,于是有

    (2 400 000 000*2)/5 = 960 000 000 (循环/秒)(这边除以5是因为上面汇编代码有5条汇编语句,尼玛书上没有说清楚,让我这样的小白想了好久……),也就是说CPU每一秒钟可以运行这个空循环960 000 000次。不过我们还是不能简单的将n=960 000 000,然后Sleep(1000)了事。如果我们让CPU工作1秒钟,然后休息1秒钟,波形很有可能就是锯齿状的一样先到达峰值(>50%),然后跌倒一个很低的占用率。

    我们尝试着降低两个数量级,令n = 9 600 000,而睡眠时间则相应的改为10毫秒。代码清单如下:

    int main()
    {
        for(; ;)
        {
            for(int i = 0; i < 9600000; i++)
                ;
            sleep(10)
        }
        return 0;
    }

    再不断调整参数后,就能得到一条大致稳定的CPU占有率直线。

    但是这个方法最大的问题是,参数都是根据特定机器算出来的。在其他机器上要重新调整,所以需要有更加智能的方法。

    之后书中给出了另外两个解法:

    解法二:使用了系统API:GetTickCount()和Sleep()

    解法三:使用工具Perfmon.exe

    具体我就不列出来了,大家有兴趣去看书吧。(我这样给这本书打广告了,大神不会告我侵权了吧……^_^)

    但是,这些方法都没有考虑到多核和多CPU的情况,书中提到对于多CPU的问题首先需要获得系统的CPU信息。可以使用GetProcessorInfo()获得多处理器的信息,然后指定进程在哪一个处理器上运行。其中指定运行使用的是SetThreadAffinityMask()函数。

    另外,还可以使用RDTSC指令获取当前CPU核心运行周期数。

    在x86平台定义函数:

    inline unsigned_int64 GetCPUTickCount()
    {
        _asm
        {
            rdtsc;
        }
    }

    在x64平台上定义:

      #define GetCPUTickCount()_rdtsc()

    使用CallNtPowerInformation API得到CPU频率,从而将周期数转化为毫秒数。(这边这段有点不知所云了……)

    这边我给出两段代码,分别用C++和Python实现,通过动态获取CPU的状态,调整线程的数量来实现让CPU保持在某一个值,且考虑了多CPU情况。

      1 #define _WIN32_WINNT 0x0502
      2 
      3 #include <cstdio>
      4 #include <cstdlib>
      5 #include <ctime>
      6 
      7 #include <Windows.h>
      8 #include <process.h>
      9 
     10 #define TARGET_CPU_RATE (80.0)
     11 
     12 extern "C" {
     13     typedef struct _CONTROL_PARAM
     14     {
     15         volatile LONG m_exit;
     16         volatile LONGLONG m_rest;
     17     } CONTROL_PARAM;
     18 };
     19 
     20 static CONTROL_PARAM g_param;
     21 
     22 unsigned __stdcall task_thread(void *pparam)
     23 {
     24     if (!pparam)
     25         return 0;
     26 
     27     CONTROL_PARAM *pctrl = (CONTROL_PARAM *)pparam;
     28 
     29     LONGLONG rest64 = 0;
     30     while (true)
     31     {
     32         if (rest64 > pctrl->m_rest)
     33         {
     34             Sleep(1);
     35             rest64=0;
     36         }
     37         else
     38             rest64++;
     39     }
     40 
     41     return 0;
     42 }
     43 
     44 inline unsigned __int64 u64_time(FILETIME &_ft)
     45 {
     46     unsigned __int64 u64;
     47     u64 = _ft.dwHighDateTime;
     48     u64 = u64 << 32;
     49     u64 |= _ft.dwLowDateTime;
     50     return u64;
     51 }
     52 
     53 int main()
     54 {
     55     SYSTEM_INFO sys_info;
     56     ZeroMemory(&sys_info, sizeof(SYSTEM_INFO));
     57 
     58     GetSystemInfo(&sys_info);
     59     int cpu_cnt = (int)sys_info.dwNumberOfProcessors;
     60 
     61     if (0 == cpu_cnt)
     62         cpu_cnt = 1;
     63     printf("cpu count: %d
    ", cpu_cnt);
     64 
     65     g_param.m_rest = (DWORD)-1;
     66     for (int i=0; i<cpu_cnt; ++i)
     67     {
     68         _beginthreadex(NULL,0,task_thread,&g_param,0,NULL);
     69     }
     70 
     71     FILETIME idleTime;
     72     FILETIME kernelTime;
     73     FILETIME userTime;
     74 
     75     FILETIME last_idleTime;
     76     FILETIME last_kernelTime;
     77     FILETIME last_userTime;
     78 
     79     bool initialized = false;
     80 
     81     while (true)
     82     {
     83         if (GetSystemTimes(&idleTime,&kernelTime,&userTime))
     84         {
     85             if (initialized)
     86             {
     87                 unsigned __int64 usr = u64_time(userTime) - u64_time(last_userTime);
     88                 unsigned __int64 ker = u64_time(kernelTime) - u64_time(last_kernelTime);
     89                 unsigned __int64 idl = u64_time(idleTime) - u64_time(last_idleTime);
     90 
     91                 double sys = ker + usr;
     92                 double cpu = (sys - (double)idl) / sys * 100.0;
     93 
     94                 double dif = TARGET_CPU_RATE - cpu;
     95                 g_param.m_rest = (LONGLONG)((double)g_param.m_rest * (1.0 + dif/100.0));
     96 
     97                 printf("rest = %I64d, cpu = %d
    ", g_param.m_rest, (int)cpu);
     98             }
     99             else
    100                 initialized = true;
    101 
    102             last_idleTime = idleTime;
    103             last_kernelTime = kernelTime;
    104             last_userTime = userTime;
    105         }
    106 
    107         Sleep(300);
    108     }
    109 
    110     return getchar();
    111 }

    Python程序:

     1 from ctypes import windll, Structure, byref, c_longlong
     2 from ctypes.wintypes import DWORD
     3 
     4 import win32api
     5 import multiprocessing
     6 
     7 WinGetSystemTimes = windll.kernel32.GetSystemTimes
     8 
     9 class FILETIME(Structure):
    10     _fields_ = [ ("dwLowDateTime", DWORD), ("dwHighDateTime", DWORD) ]
    11 
    12 def pyGetSystemTimes():
    13     idleTime, kernelTime, userTime = FILETIME(), FILETIME(), FILETIME()
    14     WinGetSystemTimes(byref(idleTime), byref(kernelTime), byref(userTime))
    15     return (idleTime, kernelTime, userTime)
    16 
    17 def longTime(ft):
    18     tm = ft.dwHighDateTime
    19     tm = tm << 32
    20     tm = tm | ft.dwLowDateTime
    21     return tm
    22 
    23 TARGET_CPU_RATE = 70.0
    24 
    25 def worker(val):
    26     rest = 0
    27     while (True):
    28         if rest > val.value:
    29             rest = 0
    30             win32api.Sleep(1)
    31         else:
    32             rest += 1
    33 
    34 if __name__ == '__main__':
    35     sys_info = win32api.GetSystemInfo()
    36     cpu_cnt = sys_info[5]
    37     
    38     val = multiprocessing.Value(c_longlong, 100, lock=False)
    39     print type(val.value)
    40     
    41     threads = []
    42     for i in range(cpu_cnt):
    43         p = multiprocessing.Process(target=worker, args=(val,))
    44         p.start()
    45         threads.append(p)
    46     
    47     initialized = False
    48     last_times = (FILETIME(), FILETIME(), FILETIME())
    49     while (True):
    50         cur_times = pyGetSystemTimes()
    51         
    52         if initialized:
    53             usr = longTime(cur_times[2]) - longTime(last_times[2])
    54             ker = longTime(cur_times[1]) - longTime(last_times[1])
    55             idl = longTime(cur_times[0]) - longTime(last_times[0])
    56             
    57             sys = float(ker + usr)
    58             cpu = (sys - float(idl)) / sys * 100.0;
    59             
    60             dif = TARGET_CPU_RATE - cpu;
    61             val.value = long(float(val.value) * (1.0 + dif / 100.0));
    62             
    63             print "rest=", val.value, ", cpu=", int(cpu)
    64         else:
    65             initialized = True
    66         
    67         last_times = cur_times
    68         win32api.Sleep(300)
  • 相关阅读:
    Redis--过期键策略(惰性删除、定期删除)
    Redis--数据库(个数16、键空间、过期字典、过期策略)
    Redis--事件(serverCron)
    ArrayList是如何扩容的?
    Java的四大引用类型
    类加载机制,双亲委派模型及其优点
    GC调优思路
    modcount的作用
    JVM的常见的垃圾收集器
    什么是临界区?如何解决冲突(也就是临界区的调度原则)?
  • 原文地址:https://www.cnblogs.com/randyyang/p/4115570.html
Copyright © 2020-2023  润新知