最近在耐着性子,看了大半的《C++应用程序性能优化》和《Efficient C++ Performance Programming Techniques》。总体感觉,虽然内容不一样,但前者就像是后者的中文化版及针对VC2003的版本。想来汉语本应言简意赅,但前者写得似乎有些啰嗦,看着反不如后者顺畅。
一直用C++,大部分时间写数值计算的程序,所以C++的性能常常挂在心中。各种语言的性能比较,各种编译器的比较,看了研究了比较了,最后的结论无非就是那几条看似没有深刻原理的经验性质的说明。当逃离BBS上的是非之地,静下心来关注一具体细节时,才发现自己的认识是多少肤浅,道听途说的东西,不可尽信。
曾以为,虚函数是OO中性能降低的原由,模板及范型编程是实现高效的唯一曙光。但真实的情况却告诉我,虚表与虚方法的查找,并未让程序降低太多性能,而这恰是保持编程通用性,可扩展性,重用性及性能的一种语言上的优良特性;“降低性能的地方”,确切地说,“没能让程序进一步提高性能的地方”是动态绑定让编译器不能内联方法,从而失去了内联带来的性能提高。
也曾以为,内联就是把方法里的代码移过来,没有了寄存器保存,没有了call,于是就没有了这些调用的开销。但事实又告诉我,这仅仅是内联最基本的好处,更多的好处在于,代码移动到调用的地方,使编译器能进行根据“上下文”进行全面的优化。
例如这样的代码:
int x;
public:
int get () ...{
return x;
}
void set (int arg) ...{
x = arg;
}
}
int main ()
... {
GatedInt gi;
gi.set(12);
cout << gi.get();
}
内联后,会优化为:
... {
cout << 12;
}
内联被称为C++程序获得性能提高的最简单途径。但实际上,是否内联的标准是比较复杂的。更有趋势说,内联与否是应由编译器自己确立的事情,终有一天我们会忽略inline。
现在终于明白,如果能真正看清C++代码的每一句,对应会产生多少行指命,优化性能也不是问题。事实是除了编译器,人是很难做到这一点的。于是只有总结各点,以不断提醒自己,注意自己写的每一行代码,究竟做了什么事情:
- 构造函数与析构函数。建立对象,只是简单的一句。Object obj或Object *pObj = new Object。但构造与析构里,做了多少耗时的操作,这是一个问题。另外,构造函数里,初使化对象的顺序,是由成员的声明的顺序唯一决定的,与初使化列表无关。但使用初使化列表,是非常好的习惯。
- 临时变量。临时变量的产生很难发现,最典型的便是矩阵加法的代码:Matrix A,B,C; C=A+B;这里不知道有多少人能清楚地指出操作中产生临时变量的个数。返回对象是产生临时变量的常见情况,还好有一“返回值优化”的方法,可以在一定程度上减少临时变量的产生。
- new/delete,默认的库除非你真正地了解它,否则为了性能,它也是不可考虑的,即便它是默认的!new/delete是线程安全的。但这在有些情况下,并不需要。除去lock之后,很容易发现,程序性能会有很大提高。
- 堆和栈。栈有cpu级的操作指令支持,因此速度很快;而堆需要一向的算法来维护,与new/delete的实现有关。因此较慢。
- 不要完全相信STL。不过,多研究一下Effective STL是有益的。
总地说来,优化程序的性能的方法并不多。但写出一个高效的程序,仍不是一件容易掌握的事情。曾经见过一网友通过自己的实验验证了i++与++i在性能上并无大异,大胆地怀疑“++i”这一经验之谈。现在看来真是好笑。每次看完书,都会发觉其实只要浏览每章最后的小节,大致的内容便能了然于心。但如果只是浏览这些关键点,就如同在BBS中看别的人经验贴一样,只会产生不成熟的怀疑或认识,稍作深入,便会发现自己的肤浅与浮躁。