• 把 CPU “玩”起来


    前言

    从开始学习编程之后,就渐渐痴迷于技术,平时遇到购书满减活动时就忍不住买一堆书。前两天闲着无聊,翻开了去年买的《编程之美》,目录里的“让 CPU 占用率听你指挥”吸引力我的眼球。这一年来捣鼓数据挖掘和机器学习,总会关注代码运行效率,偶尔会思考如何提高 CPU、GPU 的利用率。于是马上翻开了这一节。

    让 CPU 利用率听你指挥

    翻开后是一道编程题(3星,需要查阅一些资料,在60分钟内完成)

    写一个程序,让用户来决定 Windows 任务管理器(Task Manager)的 CPU 占用率。程序设计的越精简越好,语言不限。例如,可实现下面三种情况:

    1. CPU 和占用率固定在50%,为一条直线
    2. CPU 的占用率为一条直线,具体占用率由命令行参数决定(参数范围 1~100)
    3. CPU 的占用率状态是一条正弦曲线

    怎么实现呢

    稍微想了想,如果想让 CPU 跑满,写一个死循环就好了,让 CPU 一直处于运行状态,那 50% 的利用率要怎么实现呢?一半时间运行一半时间休息,emmmmm。。休息。。突然想到了多线程里常用到的 sleep。接着往下看,确实是使用 sleep。

    那就写写代码吧

    while True:
        for i in range(7200000):
            pass
        time.sleep(0.01)

    这里稍微解释下为什么是 7200000,以及为什么睡眠 0.01s(10ms)。

    笔记本的 CPU 是 1.8Ghz,每秒运行次数大概为 1.8 * 10^9 次,假设 CPU 每个时钟周期可以执行两条代码,然后对于一段 for 循环代码,转换成汇编如下

    next:
    mov     eax, dword ptr[i]     ; i放入寄存器
    add     eax, 1                ;  寄存器+1
    mov     dword ptr [i], eax    ;  寄存器赋回i
    cmp     eax, dword ptr [i]    ;  比较i和n
    j1      next                  ;  i小于n时重复循环

    即5条代码,所以,1S 内循环次数为 1.8 * 10^9 * 2 / 5 = 720000000。而睡眠 10ms 是因为接近 Windows 的调度时间片。

    运行了一下,只是稳定在 30% 左右,暂时先不调整循环次数,接着往后看。

    可以看出来,这样设置利用率很麻烦,那有没有什么方法可以快点设置呢

    重新看看上面这段代码, 7200000 次循环花费的时间大约为 10ms,那意思就是 CPU 运行 10ms 然后再休息 10ms,再运行 10ms 再休息 10ms,接着运行 10ms 然后再休息 10ms ······ 想必肯定看出来什么了吧,我们只需要设置 CPU 运行多少时间就好了!于是可以写出下面代码

    busyTime = 0.01
    while True:
        startTime = time.clock()
        while((time.clock() - startTime) <= busyTime):
            pass
        time.sleep(busyTime)

    运行一下,跟刚刚差不太多,稳定在 30% 左右

    正弦函数

    这时候,我们也可以很容易就写出跑成正弦函数图像的代码了,不断改变运行与空闲的时间比就好了。

    import time
    import mathimport affinity
    from multiprocessing import Process, cpu_count
    
    def exec_fun():
        SAMPLING_COUNT = 200 # 抽样点数量
        PI = math.pi    # pi
        TOTAL_AMPLITUDE = 300 # 每个抽样点对应时间片
        busySpan = []
    
        amplitude = TOTAL_AMPLITUDE / 2
        radianIncrement = 2.0 / SAMPLING_COUNT
        radian = 0.0
        for i in range(SAMPLING_COUNT):
            busySpan.append((amplitude + math.sin(PI * radian) * amplitude) / 1000.0)
            radian += radianIncrement
            # print(busySpan[i], TOTAL_AMPLITUDE - busySpan[i])
    
        j = 0
        while True:
            startTime = time.clock()
            # print(startTime)
            while ((time.clock() - startTime) <= busySpan[j]):
                pass
            # print('sleep')
            time.sleep(0.3 - busySpan[j])
            j  = (j + 1) % SAMPLING_COUNT
    
    exec_fun()

    运行一下。emmmmmmmmmmmm。。。。等一下,不对啊,怎么不是正弦函数形状呢?

     这跟说好的好像不太一样啊。是不是因为用的是 python,跑的本来就慢的原因?那试试 C++ 吧

    #include<stdlib.h>
    #include<Windows.h>
    #include<math.h>
    
    const int SAMPLING_COUNT = 150;
    const double PI = 3.1415926535;
    const int TOTAL_AMPLITUDE = 300;
    
    int main()
    {
        DWORD busySpan[SAMPLING_COUNT];
        int amplitude = TOTAL_AMPLITUDE / 2;
        double radian = 0.0;
        double radianIncrement = 2.0 / (double)SAMPLING_COUNT;
        for (int i = 0; i < SAMPLING_COUNT; i++) {
            busySpan[i] = (DWORD)(amplitude + sin(radian * PI) * amplitude);
            radian += radianIncrement;
            printf("%d	%d
    ", busySpan[i], TOTAL_AMPLITUDE - busySpan[i]);
        }
        DWORD startTime = 0;
        for (int j = 0;; j = (j + 1) % SAMPLING_COUNT) {
            startTime = GetTickCount();
            while ((GetTickCount() - startTime) <= busySpan[j]);
            Sleep(TOTAL_AMPLITUDE - busySpan[j]);
        }
        return 0;
    }

    再运行一下,它怎么还是这样???

    于是乎捣鼓了 2 个小时。。。

    ……

    ……

    ……

    后来仔细想了想,CPU 是 4 核 8 处理器的,不会是任务分摊到了几个处理器上了吧?于是查了查如何把当前进程放在一个处理器上执行。

    if __name__ == "__main__":
        p = Process(target=exec_fun)
        p.start()
        pid = p.pid
        print(affinity.get_process_affinity_mask(pid))
        affinity.set_process_affinity_mask(pid, 1)

    运行一下,好的,它成了!!!

     顺便解决下上面C++的代码,在 main() 函数最开始加入下面代码

    SetThreadAffinityMask(GetCurrentThread(), 1);

    小节

    好久没有这样子捣鼓过东西了,想想上次做操作系统课设的时候,要获取系统的信息,当时只是为了完成任务就没有去深究一些东西,这次捣鼓了 CPU 的利用率控制之后,对进程、CPU 以及 python 的多线程等知识又多了一点了解。感觉技术还是需要沉下心来才能学得好。

  • 相关阅读:
    csrf攻击 使用js 调用 php文件的方法(还没实践)
    优化 要引入多个 模块 使用调用的方法,让管理更便捷 --execfile() 函数
    自动化 数据分离 --A文件里面的类 中的函数 调用 B文件里面类 的函数 的方法
    断言文件-封装方法 和 思路
    appium 元素文件 -查找元素 封装思路和方法
    appium的log详细分析
    链接多个数据库的方法
    修改phpMYadmin 链接其他数据库地址的方法
    搜索字段-预约时间
    用过的sql 工具
  • 原文地址:https://www.cnblogs.com/csu-lmw/p/12229634.html
Copyright © 2020-2023  润新知