前言
从开始学习编程之后, 就渐渐痴迷于技术, 平时遇到购书满减活动时就忍不住买一堆书. 前两天闲着无聊, 翻开了去年买的《编程之美》, 目录里的 "让 CPU 占用率听你指挥" 吸引力我的眼球. 这一年来捣鼓数据挖掘和机器学习, 总会关注代码运行效率, 偶尔会思考如何提高 CPU,GPU 的利用率. 于是马上翻开了这一节.
让 CPU 利用率听你指挥
翻开后是一道编程题(3 星, 需要查阅一些资料, 在 60 分钟内完成)
写一个程序, 让用户来决定 Windows 任务管理器 (Task Manager) 的 CPU 占用率. 程序设计的越精简越好, 语言不限. 例如, 可实现下面三种情况:
CPU 和占用率固定在 50%, 为一条直线
CPU 的占用率为一条直线, 具体占用率由命令行参数决定(参数范围 1~100)
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\t%d\n", 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 的多线程等知识又多了一点了解. 感觉技术还是需要沉下心来才能学得好.
来源: https://www.cnblogs.com/csu-lmw/p/12229634.html