前面我们提到了使用编译器的优化选项进行不同级别的代码优化的方法。俗话说“好马配好鞍”,即使我们有了强大的代码优化工具,使得我们书写的符合ANSI/ISO C/C++的代码能被高效执行,我们在写代码时也要考虑到一些必要的原则,从而既能实现代码的优化,也能保证代码的安全,使得优化操作不会让我们的代码产生预期之外的结果。下面我们就来看一下在使用代码优化时,必须考虑的五大问题。
1. 小心使用汇编表达式
在C/C++代码中,有时候一些操作难免会对某些CPU寄存器进行操作,此时要使用内嵌的汇编表达式,例如asm("EALLOW"),或者重置某个中断的掩码寄存器等。在优化代码时,编译器会重新调整某些代码段的顺序,自己决定使用某些寄存器(例如AR0-AR7这样的辅助寄存器),甚至删除某些编译器认为无用的变量、函数等,但是编译器一般情况下并不会对内嵌的汇编代码进行任何优化(除非这段汇编代码被编译器认为是永远不会执行到的无用代码),这就造成了编译器的优化效果在这段汇编代码和它的上下文代码中无法进行有效的优化,特别是汇编代码和C/C++代码直接存在变量调用的情况下。所以非必要的情况下,要尽量避免C/C++和汇编语句的混用,如果确实需要的,也要在编译之后检查生成的汇编代码是不是保证了我们代码原意的完整性。2. 为必要的内存存取使用volatile关键字
在C/C++代码的编译过程中,编译器会分析数据流,从而尽量避免对存储空间的直接存取。但是如果我们要在C/C++代码中直接对内存地址进行操作的话,需要使用volatile关键字来定义变量,编译器在优化时不会对volatile类型的变量进行优化。
例如,在下面的代码中,循环的结束条件为指针指向的地址为0xFF:
unsigned int *ctrl;
while (*ctrl !=0xFF);
因为*ctrl是一个不变的表达式,这个循环会被优化为一次内存读取。为了正确实现我们的代码意图,需要把ctrl定义为volatile类型:
volatile unsigned int *ctrl
使用volatile类型定义的类型在调试的时候还有一个极大的优势,就是我们可以直接在CCS的debug窗口里改变变量的值,极大地方便我们的调试。3. 小心使用Alias变量
Alias(别名)在一个变量可以被至少两种方式存取的时候会用到,例如,当两个指针指向同一块区域或对象时,我们称一个指针 alias 另一个指针。Alias变量的使用要非常谨慎,因为会涉及到非直接的引用,从而破坏了优化效果。编译器在优化时会分析代码来决定在哪些地方会产生alias引用,然后在保持代码正确性的基础上“保守”地优化代码。
一般情况下,编译器会假设,如果一个本地变量的地址被传递给某个函数,则这个函数有可能会通过指针操作改变这个本地变量的内容,但是这个函数不能在该地址被返回后仍然可以被别的指针操作所示使用,例如把这个本地变量的地址分配给一个全局变量或者返回它。如果这种假设被打破,则需要在编译器选项里使用-ma强制编译器按照最坏情况的别名引用来进行一定的优化,在这种情况下,任何非直接的引用(例如使用指针)都可以引用到这个变量。
4. 何时使用--aliased_variables选项?
编译器在优化时会假设,任何变量的地址在作为参数被传递给某个函数时,都不会在调用它的函数里被任何Alias变量修改,例如:从函数返回地址,或者把地址分配给某个全局变量。但是如果我们使用管理类似下面操作的代码的时候,就需要使用--aliased_variables选项来优化代码:
int *glob_ptr;
g()
{
int x = 1;
int *p = f(&x);
*p = 5; /* p是x的别名 */
*glob_ptr = 10; /* glob_ptr 也是 x的别名 */
h(x);
}
int *f(int *arg)
{
glob_ptr = arg;
return arg;
}
5. 含有FPU的器件:使用restrict关键词来表明指针没有被别名操作
在含有FPU浮点协处理器的器件中,当使用--opt_level=2(优化寄存器、局部变量和全局变量的使用)的优化级别时,优化器会分析代码的依赖性。为了更好地帮助优化器完成内存的依赖关系,我们可以把指针、引用或者数组等的声明中加入restrict关键词。restrict关键词是C99标准引入的,它会通知编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改。使用这一原则能帮助编译器优化某些代码段,因为别名信息可以被更加快速地确认。使用restrict关键词后因为可以执行更多的FPU操作,而FPU操作与CPU是并行的,所以带来的优化效果是提高了性能和减小了代码尺寸。
我们可以使用下面例子中的代码来告诉编译器,a和b永远不会在函数foo中指向同一个地址,并且a和b的内存地址也不会互相覆盖(说明它们没有依赖性,可以并行执行)。void foo(float * restrict a, float * restrict b)
{
/* foo's code here */
}
或者
void foo(float c[restrict], float d[restrict])
{
/* foo's code here */
}