写程序稍微有点年头的人都知道,一般情况下CPU的位运算比乘除运算是快很多的,比如说k %= 64,完全能够写成 k &= 63来达到加速的目的,k *=2也完全可以写成k«= 1.在电脑上这东西可能还没有那么严重,因为CPU原生了比较快的一个乘法、除法指令,在写嵌入式程序的时候尤其是不带硬件乘法器、除法器的时候用这些小技巧能够省下不少时间,对于那种一秒钟触发几万、几十万次的操作来说,这样子能够省下很大的CPU资源。

我们都知道的道理,写编译器的人当然也知道,我一直在想,如果我在程序里面写了一句k %= 64,编译器到底会不会帮我优化成k &= 63,今天突然奇想,准备看一下。

先试了一下最近在用的MDK,由于MDK的优化级别比较高,为了不让我的费语句被编译器优化掉,我写了总共五句话来告诉编译器“这个t变量是实实在在起作用了的,求求你别优化”掉- -
	int t;
	t = rand();
	t %= 64;
	srand(t);
	t = rand();

汇编之后结果如下:

    11:         int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t %= 64;
0x08000438 4620      MOV      r0,r4
0x0800043A EA4F71E4  ASR      r1,r4,#31
0x0800043E EB046191  ADD      r1,r4,r1,LSR #26
0x08000442 EA4F11A1  ASR      r1,r1,#6
0x08000446 EBA41481  SUB      r4,r4,r1,LSL #6
    14:         srand(t);
0x0800044A 4620      MOV      r0,r4
0x0800044C F002FB88  BL.W     0x08002B60
    15:         t = rand();

发现结果很神奇,编译器既没有简单的把%编译为除法,也没有编译为与运算达到优化的目的,而是有一大堆语句单周期指令,灵光一闪,难道是因为有符号数,所以编译器要考虑的东西比较多?于是把int t改成了unsigned t,再看一下。

    11:         unsigned int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t %= 64;
0x08000438 F004043F  AND      r4,r4,#0x3F
    14:         srand(t);
0x0800043C 4620      MOV      r0,r4
0x0800043E F002FB89  BL.W     0x08002B54
    15:         t = rand();

果然不出意料,%被优化到只有一句简单的&

接下来试试除法运算,既然都改成unsigned int了,就先试无符号数吧

    11:         unsigned int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t /= 64;
0x08000438 EA4F1494  LSR      r4,r4,#6
    14:         srand(t);
0x0800043C 4620      MOV      r0,r4
0x0800043E F002FB89  BL.W     0x08002B54
    15:         t = rand();

不出所料,编译器很聪明的优化成了一个逻辑右移,再试试乘法

    11:         unsigned int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t *= 64;
0x08000438 EA4F1484  LSL      r4,r4,#6
    14:         srand(t);
0x0800043C 4620      MOV      r0,r4
0x0800043E F002FB89  BL.W     0x08002B54
    15:         t = rand();

很顺利,优化成了左移

对于非2的整数次幂会怎么样,比如48?试试看!

    11:         unsigned int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t *= 48;
0x08000438 EB040044  ADD      r0,r4,r4,LSL #1
0x0800043C EA4F1400  LSL      r4,r0,#4
    14:         srand(t);
0x08000440 4620      MOV      r0,r4
0x08000442 F002FB89  BL.W     0x08002B58
    15:         t = rand();

看来编译器十分聪明,通过位运算的办法间接实现了乘法运算,我们知道,48在2进制下面是110000,因为只有2个1,所以可以这么干,来试试1比较多的情况,比如10101010,也就是170

    11:         unsigned int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t *= 170;
0x08000438 F04F00AA  MOV      r0,#0xAA
0x0800043C FB04F400  MUL      r4,r4,r0
    14:         srand(t);
0x08000440 4620      MOV      r0,r4
0x08000442 F002FB89  BL.W     0x08002B58
    15:         t = rand();

编译器表示无能为力了,直接给我来了一条mul指令,不过实际上由于stm32是带了单周期硬件乘法器的,所以mul指令反而很快……

接下来试试有符号数的情况

先是除法

    11:         int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t /= 64;
0x08000438 4620      MOV      r0,r4
0x0800043A EA4F71E4  ASR      r1,r4,#31
0x0800043E EB046191  ADD      r1,r4,r1,LSR #26
0x08000442 EA4F14A1  ASR      r4,r1,#6
    14:         srand(t);
0x08000446 4620      MOV      r0,r4
0x08000448 F002FB88  BL.W     0x08002B5C
    15:         t = rand();

嗯嗯,编译器通过四句单周期指令间接实现了除法,主要还是因为多了负数的情况

看看乘法的情况

    11:         int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t *= 64;
0x08000438 EA4F1484  LSL      r4,r4,#6
    14:         srand(t);
0x0800043C 4620      MOV      r0,r4
0x0800043E F002FB89  BL.W     0x08002B54
    15:         t = rand();

编译器利用了LSL指令一次性实现了乘法的运算,试试乘以48

    11:         int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t *= 48;
0x08000438 EB040044  ADD      r0,r4,r4,LSL #1
0x0800043C EA4F1400  LSL      r4,r0,#4
    14:         srand(t);
0x08000440 4620      MOV      r0,r4
0x08000442 F002FB89  BL.W     0x08002B58
    15:         t = rand();

跟无符号数类似,170也试试

    11:         int t;
0x08000430 B508      PUSH     {r3,lr}
    12:         t = rand();
0x08000432 F7FFFED3  BL.W     0x080001DC
0x08000436 4604      MOV      r4,r0
    13:         t *= 170;
0x08000438 F04F00AA  MOV      r0,#0xAA
0x0800043C FB04F400  MUL      r4,r4,r0
    14:         srand(t);
0x08000440 4620      MOV      r0,r4
0x08000442 F002FB89  BL.W     0x08002B58
    15:         t = rand();

不出意料,它放弃优化了。

结论:为了能够让编译器达到比较好的优化效果,能够用无符号数的地方就别用有符号数。

文章比较长了,Visual Studio 2010中的测试再开一篇吧。