1.1 让CPU占用率曲线听你指挥
题意:写程序来控制任务管理器中CPU的占用率,使得占用率曲线达到某种样式。
任务管理器是通过定期对CPU的状态采样来获得占用率的,如果我们保证在采样间隔内占用率相同,那么曲线就会是一条直线。
由于CPU的工作状态是离散的,我们是无法做到真正的50%占用的这种状态,指令只有执行和不执行之分,没有执行一半的说法。我们只能努力使得在一个微分的时间片内,CPU有50%的时间工作,50%的时间空闲,这样从宏观上来看,的确是50%的占用。
书中第一个例子的缺点是依赖于具体的CPU,在一个时间片内,它使用Sleep来设定10ms的占空时间,使用CPU的频率来估计工作的时间。
第二个例子则好得多,使用Tick来对工作时间计时,脱离了对具体频率的依赖。
第三个例子是为了能在有其他进程干扰下正常工作,因为前两个例子并不能保证自己Sleep的时候,其他进程也不工作。不过这种适应实际上只满足了一半的情况,还是以50%占用的目标为例,其他进程占10%,它能补40%,但其他进程占60%的时候,程序不可能把占用率降下来,程序只能补,不能减。
第四个正弦曲线的例子实际上就是不断调整占空比来实现的,原理和第二个例子完全一样。
对于多核心的情况,涉及的无非是核心管理的问题,懂一些系统API就行,这个题目的关键还是微分时间片的思想。
1.2 中国象棋将帅问题
题意:懂中国象棋的人都知道,将和帅是不能碰面的,这道题就是要求编程打印出这种情况下,将和帅的所有可能的位置,只能使用一个变量。
打印出所有位置的确是个简单的问题,这道题的考点在于只使用一个变量。
本需要多个变量来保存状态,却只能使用一个变量,无非两种解答方案:1、把一个长变量拆成多个短变量用;2、编码变量降维。
变量存的是信息,以上两种方案归结起来都是把信息存到一个变量的空间里面去,前者使用空间来划分信息,后者利用逻辑关系来编码信息。
书中完整解法只给了一个,后面附带了两段代码,解法一和解法三无本质区别,都是将长变量划分成短变量来用,只是前者手工划分,后者利用了结构体划分,解法二是一种非常有趣的解法,将帅的位置编号分别有9个,恰好可以写成一个9×9的矩阵,矩阵里面的81个元素对应81种状态,状态值能够对应到矩阵中的位置,因此可以遍历所有将帅位置的组合。
解法二实际上是将二维状态线性化编码,本来81个二维向量,编码成了81个数字,然后能够解码对应到二维向量。其中,在解码时候使用了除法和模运算,传统上认为这是非高效的指令。
解法一手工划分太麻烦,还不如解法三直接。不过解法三也使用了模运算,可不可以不使用模运算呢?
分析下题目我们发现,将帅的位置是线性编码的,也就是说,为了判断将帅是不是面对面了,还得解码得出列位置,然后才能判断。那么,我们可以直接将将帅位置二维化编码,分别保存行列,即使这样编码,一个字节也足够了。这里我提高了问题维度,但减少了一次解码过程。
#include <stdio.h> struct { unsigned char x1:2; unsigned char y1:2; unsigned char x2:2; unsigned char y2:2; }i; int main() { for(i.x1 = 0; i.x1 < 3; i.x1++) { for(i.x2 = 0; i.x2 < 3; i.x2++) { if(i.x1 == i.x2) continue; for(i.y1 = 0; i.y1 < 3; i.y1++) { for(i.y2 = 0; i.y2 < 3; i.y2++) { printf("A = (%d, %d), B = (%d, %d)\n", i.x1, i.y1, i.x2, i.y2); } } } } return 0; }
从只使用一个变量的思考中,我发现这些有趣的规则:1、对于固定维度的问题,如果变量有限,既可以划分变量匹配问题维度,也可以编码变量降低维度;2、问题本身的维度也和其编码定义有关。
维度和编码的取舍,其实就是空间和时间的取舍。