1 继续测试性能(harib11a ~ harib11c)

昨天的harib10i程序的执行结果,让笔者很不甘心,所以一早赶紧在真机上运行一下,看看效果。

真机上的运行结果比较

harib10g: 0099969263

harib10i: 0099969264

哦,几乎没什么不同。看来数值变小好像是由于模拟器的原因(Windows的原因?)。程序本身并没有问题,暂时松了一口气。

虽然放心了一些,但终究还是不满意。速度没有变慢,这当然令人欣慰。但我们费了半天劲,做了各种修改之后,与harib10g比较起来,速度却完全没有变快,大家不觉得很不甘心吗?至少笔者很不甘心。

然而当我们仔细思考一下后,会发现harib10h的改进之处在于“消除了移位处理”。也就是说,对于发生很多“移位”的情况,改进应该是有效果的。只有在大量使用定时器的时候,才会发生很多“移位”。如今只使用3个定时器,看不到改进效果也是理所当然的。所以,如果我们特意使用大量定时器,然后进行性能比较,改进效果或许能让人满意(笑)。

■■■■■

既然这样,我们就赶快试一下吧。在timer.c中,最多可以设定500个定时器。设定起动50天后1才超时的定时器490个左右,使得50天后,每隔1秒就有一个定时器超时。设定这么多定时器以后,有没有“移位”,就能从数字上看出来。

1 为什么是50天后呢?因为我们并不考虑定时器超时的情况,所以为了不让其超时,先暂时设定为50天。虽然设成1天以后也可以,但如果谁启动后一直开着,任其运行的话,可能会造成超时而导致误操作,所以还是设为50天吧。

本次的bootpack.c节选

  1. void set490(struct FIFO32 *fifo, int mode)
  2. {
  3. int i;
  4. struct TIMER *timer;
  5. if (mode != 0) {
  6. for (i = 0; i < 490; i++) {
  7. timer = timer_alloc();
  8. timer_init(timer, fifo, 1024 + i);
  9. timer_settime(timer, 100 * 60 * 60 * 24 * 50 + i * 100);
  10. }
  11. }
  12. return;
  13. }

还有,在设定HariMain的timer~timer3之前,要先加入“set490(&fifo,1);”语句。这样就可以追加490个定时器了。

那么,我们在timer.c中作出各种设定,分别运行“make install”,比较它们在真机上运行的结果。另外,还可以将“set490(&fifo,1);”替换成“set490(&fifo,0)”,或者是什么语句也不加入(即不修改造),然后测定它们在真机上的运行结果。我们分别运行5次,取平均值归纳如下。

真机上运行结果的比较

  • 追加490个定时器时的值 set490(&fifo, 1);

harib11a:0096521077……harib10g里加入set490的时候(有移位)

harib11b:0096522038……harib10h里加入set490的时候(没有移位、没有哨兵)

harib11c:0096522097……harib10i里加入set490的时候(没有移位、有哨兵)

  • 不追加490个定时器时的值 set490(&fifo, 0);

harib11a:0096522095……harib10g里加入set490的时候(有移位)

harib11b:0096522038……harib10h里加入set490的时候(没有移位、没有哨兵)

harib11c:0096522101……harib10i里加入set490的时候(没有移位、有哨兵)

  • 参考:不加入set490语句时的值

harib10g:0099969263……(有移位)

harib10h:0099969184……(没有移位、没有哨兵)

harib10i:0099969264……(没有移位、有哨兵)

让我们好好观察一下这个结果吧。

■■■■■

首先观察一下(1),也就是追加490个定时器的情况。取消移位则速度变快(差是961)。可以看出,线性表对于性能改善有效果。太好了!而且使用哨兵也有效果,虽然只是一点点,但速度还是变快了(差是59)。使用哨兵不仅精简了程序,也加快了速度,一举两得。

再观察一下(2)也就是不追加490个定时器的情况。没有哨兵时,取消移位反而导致速度变慢(差是57)。而使用哨兵时取消移位,速度就可以追上了(虽然也可以说是“超过”,不过只超了6,所以还是说“相同”吧)。

另一方面,我们比较一下(1)的harib11c和(2)的harib11c。结果值几乎一样。在这个没有移位的方法中,无论使用的定时器的数量是多少,性能都没有变化。至于harib11a,因为定时器的数量差别,性能上的差异会达到1018(计数结果差别是1018)。而harib11b好像也与定时器的数量无关。因此我们取消移位是正确的。

最后我们看一下(3)的结果。由于取消了移位,性能(即计数结果)上有79左右的下降,不过使用了哨兵就能恢复性能。

■■■■■

但是,对于(2)和(3),处理上虽然完全相同,而结果却相差了345万左右。这个差别实在是太大了,不可思议。……到底怎么回事儿呢?这不是程序内容的问题,而是C编译器的问题。实际上,由于跳转目标地址不同,CPU的JMP指令执行的时钟周期数也不相同。在HariMain中,循环执行“count++;”的for语句虽然最终被编译为JMP指令执行,但如果前面加上“set490(&fifo,0);”语句,那么以后各个指令的地址也都会相应地错开几个字节,结果造成JMP指令的地址也略有变化。因此执行时间也稍稍延迟,执行结果大约变差了3%。

for语句被编译后的结果

  1. for(;;) { L2
  2. count++; count++;
  3. 任意语句; -> 任意语句;
  4. } JMP L2
  5. 这个L2的地址一旦变化,JMP的执行时间就变化!

这一次虽然执行速度慢了,但也有时候,追加命令以后,会变快。如果用nask来编写HariMain,JMP指令的跳转目标的地址可以根据情况自动调整,这个问题就不会发生了。但仅仅是为了达到这个目的(即确认JMP地址能自动调整,以达到自我满足的目的),而用nask来改写HariMain的话,就有些劳师动众了,所以这次我们没做。

再来跟大家说一段插曲吧。最初笔者只创建了程序(1)和(3)。看到(1)的harib11c和(3)的harib10i的结果竟然有着如此巨大的差异,笔者也是出了一身冷汗。笔者之前的预测是,不使用移位而使用哨兵的话,即使定时器的数目再多,执行时间也不应该有什么变化。但结果却出乎意料。为此笔者几乎认定timer.c中有bug,并苦恼了好几个小时(苦笑)。最后,笔者怎么想都觉得timer.c没有问题,于是就去查看bootpack.c编译而成的机械语言(bootpack.lst)。经过比较才想起,原来JMP指令跳转目标的地址不同,执行时间也不相同。因此笔者立即创建了动作与(3)相同而JMP指令的地址不变的程序(2),并进行了测定。

令人高兴的是,这样做之后,结果正如笔者最初所预想的那样,在“无移位+哨兵”的情况下,即使定时器的数目变化了,处理速度也不变。真是太好了。之前笔者还老担心是否要改写昨天的内容呢。

COLUMN-8 如此细微的改进有意义吗?

前天和昨天我们对timer.c进行了改进,又是使用timers[ ],又是使用线性表,还使用了哨兵,花了不少时间。可得到的改善只有一点点。

00965210770096522097 在(1)中,harib11aharib11c

差值只有1020,这个差值和整体性能相比只有可怜的0.001%。

有人会注意到0.001%吗?我们买瓶装饮料的时候,假设商店的货架上,有一瓶比其他的多出一滴,会有人真正考虑那是哪一瓶吗?我们这里谈到的0.001%,简直就像2升的饮料多出来的一滴一样。……再举个更实际的例子,一件工作需要1个小时完成,为了早完成0.9秒,你愿意下这么大工夫吗?

这类批判性的思考方式基本上也是正确的,也就是说,要想加快整体上的速度,不应该去做这种费力不讨好的事,而应该把精力集中在简单而效果明显的方法上,比如将FIFO8改成 FIFO32(效果有1.3倍吧)。

但是希望大家回想一下,现在我们不是在加快整体的动作速度,而是一直在努力缩短哪怕是一点点的中断禁止时间。我们真正想实现的是这样的操作系统:一按下按键立即就有反应,一动鼠标立即就有反应,一旦设定了动作的时间点就会在那个时间点动作(没有些许的延迟)。

这里我们讨论的1020这个差,恰好就意味着中断禁止时间缩短了。也就是说,系统对中断的反应能力提高了。即使是在不凑巧,很多中断几乎同时发生的情况下,系统也能够更快地对这些中断要求做出反应。要是从这一点来考虑的话,1020的差异可就不是一个小数了,而是很有意义的成果。