整型数 / Integers
在我们知道使用的数不可能是负数的时候,应该使用unsigned int取代int,一些处理器处理整数算数运算的时候unsigned int比int快,于是,在一个紧致的循环里面定义一个整型变量,最好这样写代码: register unsigned int variable_name; 然而,我们不能保证编译器会注意到那个register关键字,也有可能,对某种处理器来说,有没有unsigned是一样的。这两个关键字并不是可以在所有的编译器中应用。记住,整形数运算要比浮点数运算快得多,因为处理器可以直接进行整型数运算,浮点数运算需要依赖于外部的浮点数处理器或者浮点数数学库。我们处理小数的时候要精确点些(比如我们在做一个简单的统计程序时),要限制结果不能超过100,要尽可能晚的把它转化成浮点数。
除法和余数 / Division and Remainder
在标准的处理器中,根据分子和分母的不同,一个32位的除法需要20-140个时钟周期来执行完成,等于一个固定的时间加上每个位被除的时间。 Time (分子/ 分母) = C0 + C1* log2 (分子/分母) = C0 + C1 * (log2 (分子) - log2 (分母)).
现在的ARM处理器需要消耗20+4.3N个时钟周期,这是一个非常费时的操作,要尽可能的避免。在有些情况下,除法表达式可以用乘法表达是来重写。比方说,(a/b)>c可以写成a>(c*b),条件是我们已经知道b为非负数而且b*c不会超过整型数的取值范围。如果我们能够确定其中的一个操作数为unsigned,那么使用无符号除法将会更好,因为它要比有符号除法快得多。
合并除法运算和取余运算 / Combining division and remainder
在一些情况下,除法运算和取余运算都需要用到,在这种情况下,编译器会将除法运算和取余运算合并,因为除法运算总是同时返回商和余数。如果两个运算都要用到,我们可以将他们写到一起 int func_div_and_mod (int a, int b) { return (a / b) + (a % b);} 除数是2的幂的除法和取余 / Division and remainder by powers of two
如果除法运算中的除数是2的幂,我们对这个除法运算还可以进一步优化,编译器会使用移位运算来进行这种除法运算。所以,我们要尽可能调整比例为2的幂(比方说要用64而不用66)。如果是无符号数,它要比有符号的除法快得多。 typedef unsigned int uint; uint div32u (uint a) { return a / 32; } int div32s (int a) { return a / 32; }
这两种除法都会避免调用除法函数,另外,无符号的除法要比有符号的除法使用更少的指令。有符号的除法要耗费更多的时间,因为这种除法是使最终结果趋向于零的,而移位则是趋向于负无穷。
取模运算的替换 / An alternative for modulo arithmetic
我们一般使用取余运算进行取模,不过,有时候使用 if 语句来重写也是可行的。考虑下面的两个例子: uint modulo_func1 (uint count) { return (++count % 60); } uint modulo_func2 (uint count) { if (++count >= 60) count = 0; return (count); }
第二个例子要比第一个更可取,因为由它产生的代码会更快,注意:这只是在count取值范围在0 – 59之间的时候才行。
但是我们可以使用如下的代码(笔者补充)实现等价的功能:
uint modulo_func3 (uint count) { if (++count >= 60) count %= 60; return (count); }
使用数组索引 / Using array indices
假设你要依据某个变量的值,设置另一个变量的取值为特定的字符,你可能会这样做: switch(queue) { case 0 : letter = 'W'; break; case 1 : letter = 'S'; break; case 2 : letter = 'U'; break; }
或者这样: if(queue == 0) letter = 'W'; else if ( queue == 1 ) letter = 'S'; else letter = 'U';
有一个简洁且快速的方式是简单的将变量的取值做成一个字符串索引,例如: static char *classes = "WSU"; letter = classes[queue];
全局变量 / Global variables
全局变量不会被分配在寄存器上,修改全局变量需要通过指针或者调用函数的方式间接进行。所以编译器不会将全局变量存储在寄存器中,那样会带来额外的、不必要的负担和存储空间。所以在比较关键的循环中,我们要不使用全局变量。
如果一个函数要频繁的使用全局变量,我们可以使用局部变量,作为全局变量的拷贝,这样就可以使用寄存器了。条件是本函数调用的任何子函数不使用这些全局变量。
举个例子: int f(void); int g(void); int errs; void test1(void) { errs += f(); errs += g(); } void test2(void) { int localerrs = errs; localerrs += f(); localerrs += g(); errs = localerrs; }
可以看到test1()中每次加法都需要读取和存储全局变量errs,而在test2()中,localerrs分配在寄存器上,只需要一条指令。 使用别名 / Using Aliases
考虑下面的例子: void func1( int *data ) { int i; for(i = 0; i < 10; i++) anyfunc(*data, i); }
即使*data从来没有变化,编译器却不知道anyfunc()没有修改它,于是程序每次用到它的时候,都要把它从内存中读出来,可能它只是某些变量的别名,这些变量在程序的其他部分被修改。如果能够确定它不会被改变,我们可以这样写: void func1( int *data ) { int i; int localdata; localdata = *data; for(i=0; i<10; i++) anyfunc(localdata, i); }
这样会给编译器优化工作更多的选择余地。
|