效率
80-20法则
一个程序80%的资源用于20%的代码身上。
包括:
- 80%的执行时间花在20%的代码身上。
- 80%的内存被20%的代码使用。
- 80%的磁盘访问动作被20%的代码执行。
- 80%的维护力气花在20%的代码上面。
要找到性能的瓶颈,可行之道就是完全根据观察或实验来识别出造成你心痛的那20%代码,而可行之道就是借助某个程序分析器(profiler)。
考虑使用缓式评估(lazy evaluation)
lazy evaluation 在可在多种场合派上用场。
- 引用暂代复制(copy-on-write)
在你真正需要之前,不必着急为某物做一个副本。取而代之的是,以延迟战术应付–只要能够,就使用其他副本。在某些应用领域,你常有可能永远不需要提供那样一个副本。
- 缓式取出(lazy fetch)
在产生一个大对象的时候,只产生该对象的“外壳”,不从磁盘读取任何字段数据。当对象内某个字段被需要了,程序才从数据库取回对应的数据。
对象内每个字段都是指针,指向必要的数据,而构造函数负责将每个字段初始化为null,这样的null象征着字段数据尚未从数据库读取。
- 缓式表达式求值(lazy expression evaluation)
Matrix m1(1000, 1000);//非常大的1000x1000矩阵
Matrix m2(1000, 1000);//非常大的1000x1000矩阵
Matrix m3 = m1 + m2;//这一行是不用求值的!
Matrix m4(1000, 1000);
m3 = m4 * m1;
try_visit(m3);
//m3的数据结构可能只是两个矩阵指针(指向操作数)和一个enum(值为运算类型)。
//只有当m3被用到时,才真正对m3进行求值。
分期摊还预期的计算成本
让程序超前进度地做“要求以外”的更多工作。此条款背后的哲学可称为超急评估(over-eager evaluation):在被要求之前就先把事情做下去。
over-eager evaluation 背后的观念是,如果你预期程序常常会用到某个计算,你可以降低每次计算的成本,办法就是设计一份数据结构以便能够极有效地处理需求。
Cache:将已经计算好而有可能再被需要的数值保留下来。
Prefetch:一次读取一大块数据比多次读取小块数据更快。此外经验显示,如果某处数据被需要,通常其邻近的数据也会被需要。
临时对象优化
C++的真正临时对象是不可见的–不会在源代码中出现,代码中定义的名为”temp”的这样的对象只是一个局部对象。
只要你产生一个non-heap对象而没有为它命名,便诞生了一个临时对象。这样的匿名对象通常发生于两种情况。
- 当隐式类型转换被施行起来以求函数调用能够成功。(CRE:实参类型隐式转换为形参类型)(仅当值传递以及const引用传递时)
- 当函数返回对象的时候。(可以RVO优化)
- 协助完成返回值优化(RVO)
返回所谓的constructor arguments以取代对象。
- 利用重载(overload)避免隐式类型转换
不过,增加一大堆重载函数不一定是好事,除非你有好的理由相信,使用重载函数后,程序的整体效率能获得重大改善。
- 考虑以操作符复合形式(op=)取代单独形式(op)
一般而言,复合操作符比起对应的单独版本效率更高,因为单独版本通常必须返回一个新对象,而我们必须因此负担一个临时对象的析构和构造成本。而复合版本则是直接将结果写入左端自变量,不需要产生临时对象。
可以将复合形式为基础来实现单独形式。(CRE:例如
operator+
调用operator+=
)
考虑使用其他程序库
不同的程序库即使提供相同的机能,也往往表现出不同的性能取舍策略,所以一旦你(通过profiler)找出程序的瓶颈,你应该思考是否有可能因为改用另一个程序库而消除了那些瓶颈。
了解虚函数、多继承、虚基类、RTTI的成本
参考《C++对象模型》。
(END)