C++代码优化方法总结(四)
cppbug cpp_bug@hotmail.com
五. 速度优化
在一些对速度要求非常苛刻的应用系统中,每一个CPU周期都是要争取的。这个部分展现了一些简单方法来进行速度优化。
1. 使用类来包裹长的参数列表
一个函数调用的负担将会随着参数列表的增长而增加。运行时系统不得不建立堆栈来存储参数值;通常,当参数很多的时候,这样一个操作就会花费很长的时间。
把参数列表包裹进一个单独的类中并且通过引用进行传递,这样将会节省很多的时间。当然,如果函数本身就很长,那么建立堆栈的时间就可以忽略了,因此也就没有必要这样做。然而,对于那些执行时间很短而且经常被调用的函数来说,包裹一个长的参数列表在对象中并且通过引用传递将会提高性能。
2. 寄存器变量
register specifier被用来告诉编译器一个对象将被会非常多的使用,可以把它放入寄存器中。例如:
void f()
{
int *p = new int[3000000];
register int *p2 = p; //store the address in a register
for (register int j = 0; j<3000000; j++)
{
*p2++ = 0;
}
//...use p
delete [] p;
}
循环计数是应用寄存器变量的最好的候选者。当它们没有被存入一个寄存器中,大部分的循环时间都被用在了从内存中取出变量和给变量赋新值上。如果把它存入一个寄存器中的话,将会大大减少这种负担。需要注意的是,register specifier仅仅是对编译器的一个建议。就好比内联函数一样,编译器可以拒绝把一个对象存储到寄存器中。另外,现代的编译器都会通过把变量放入寄存器中来优化循环计数。Register storage specifier并不仅仅局限在基本类型上,它能够被应用于任何类型的对象。如果对象太大而不能装进寄存器的话,编译器仍然能够把它放入一个高速存储器中,例如cache。
用register storage specifier声明函数型参将会是建议编译器把实参存入寄存器中而不是堆栈中。例如:
void f(register int j, register Date d);
3. 把那些保持不变的对象声明为const
通过把对象声明为const,编译器就可以利用这个声明把这样一个对象放入寄存器中。
4. Virtual function的运行期负担
当调用一个virtual function,如果编译器能够解决调用的静态化,将不会引入额外的负担。另外,一个非常短的虚函数可以被内联处理。在下面这个例子中,一个聪明的编译器能够做到静态调用虚函数:
#include <iostream>
using namespace std;
class V
{
public:
virtual void show() const { cout<<"I'm V"<<endl; }
};
class W : public V
{
public:
void show() const { cout<<"I'm W"<<endl; }
};
void f(V & v, V *pV)
{
v.show();
pV->show();
}
void g()
{
V v;
f(v, &v);
}
int main()
{
g();
return 0;
}
如果整个程序出现在一个单独的编译单元中,编译器能够对main()中的g()进行内联替换。并且在g()中f()的调用也能够被内联处理。因为传给f()的参数的动态类型能够在编译期被知晓,因此编译器能够把对虚函数的调用静态化。但是不能保证每个编译器都这样做。然而,一些编译器确实能够利用在编译期获得参数的动态类型从而使得函数的调用在编译期间就确定了下来,避免了动态绑定的负担。
5. Function objects VS function pointers
用function objects取代function pointers的好处不仅仅局限在能够泛化和简单的维护性上。而且编译器能够对function object的函数调用进行内联处理,从而进一步的增强了性能
六. 最后的求助
迄今为止为大家展示的优化技术并没有在设计以及代码的可读性上做出妥协。事实上,它们中的一些还提高了软件的稳固性和可维护性。但是在一些对时间和内存有严格限制的软件开发中,上面的技术可能还不够;有可能还需要一些会影响软件的可移植性和扩展性的技术。但是这些技术只能在所有其他的优化技术都被应用但是还不符合要求的情况下使用。
1. 关闭RTTI和异常处理支持
当你导入纯C代码给C++编译器的时候,你可能会发现有一些性能上的损失。这并不是语言或者编译器的错误,而是编译器作出的一些调整。如果你想获得和C编译器同样的性能,那么请关闭编译器对RTTI以及异常处理的支持。为什么会这样呢?因为为了支持RTTI和异常处理,C++编译器会插入额外的代码。这样就增加了可执行体的大小,从而使得效率有所下降。当应用纯C代码的时候,那些额外的代码是不需要的,所以你可以通过关闭来避免它。
2. 内联汇编
对时间要求苛刻的部分可以用本地汇编来重写。结果可能是速度上的显著提高。然而,这个方法不能想当然的就去实施,因为它将使得将来的修改非常的困难。维护代码的程序员可能对汇编并不了解。如果想要把软件运行于其他平台也需要重写汇编代码部分。另外,开发和测试汇编代码是一件辛苦的工作,它将花费更长的时间。
3. 直接和操作系统进行交互
API函数可以使你直接与操作系统进行交互。有时,直接执行一个系统命令可能会快许多。出于这个目的,你可以使用标准函数system()。例如,在一个dos/windows系统下,你可以这样显示当前目录下的文件:
#include <cstdlib>
using namespace std;
int main()
{
system("dir"); //execute the "dir" command
}
注意:这里是在速度和可移植性以及可扩展性之间做出的折衷。
(全文完)