本篇笔记包含 Term 16~24。
Term 16:谨记 80-20 法则
一个程序 80%的资源用于 20%的代码身上。依靠经验和程序分析器(program profiler),判断程序的瓶颈所在。
Term 17:考虑使用 lazy evaluation(缓式评估)
一个例子,linux 中创建子进程的时候,不会将父进程的资源复制给子进程,而是让子进程共享父进程的资源。如果我们只读取而没有修改数据,则子进程永远不需要复制一份资源,当需要修改数据时,内核才会为子进程复制一份资源。
不仅是复制,矩阵运算,数据库查询等等,可能我们并不需要全部的结果,只需要一部分即可。
Term 18:分期摊还预期的计算成本
一个例子,Python 中的 list 的 length 属性是作为变量存储在对象当中,并且在数据长度发生变化时同步更新,这样使得 len(list)
的时间复杂度为 O(1)。
Term 19:了解临时对象的来源
- 当对象以 by value (传值)方式传递,或是当对象被传递给一个reference-to-const 参数时,编译器会创建一个临时对象使得函数成功调用
- 函数 by value 返回一个对象时,也是临时变量
- 字面量也是临时变量
Term 20:协助完成“返回值优化(RVO)”
我们知道函数不应该返回函数中创建的变量的指针或者引用,只能 by value 返回。
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
如果我们以类似上面这种形式返回一个构造函数的调用,那么结果不会出现临时变量再被复制一次的情况,这种做法叫做返回值优化,已经被编译器广泛使用,我们不需要操心去编写构造函数形式的返回值了。甚至在现代 C++ 中我们还可以使用右值引用直接绑定临时变量。
Term 21:利用重载技术(overload)避免隐式类型转换(implicittype conversions)
为了避免函数调用(包括操作符函数)时隐式类型转换产生临时变量,我们可以编写多个函数,提供不同的参数类型完成相同逻辑的操作。
Term 22:考虑以操作符复合形式(op=)取代其独身形式(op)
操作符的“复合版本”(例如,operator+=)比其对应的“独身版本”(例如,operator+)有着更高效率的倾向,因为不需要创建对象存放结果。身为一位程序库设计者,你应该两者都提供,通常的做法是在“独身版本”中调用“复合版本”,提高代码复用;身为一位应用软件开发者,如果性能是重要因素的话,你应该考虑以“复合版本”操作符取代其“独身版本”。
Term 23:考虑使用其他程序库代替标准库
例如 iostream 程序库具有类型安全(type-safe)特性,并且可扩充。然而在效率方面,iostream 通常表现得比 stdio 差,因为 stdio 的可执行文件通常比 iostreams更小也更快。
Term 24:了解 virtual functions、multiple inheritance、virtualbase classes、runtime type identification的成本
当一个虚函数被调用,执行的代码必须对应于“调用者(对象)的动态类型”。对象的 pointer 或reference,其类型是无形的,编译器如何很有效率地提供这样的行为呢?大部分编译器使用所谓的 virtual tables 和 virtual table pointers——此二者常被简写为 vtbls 和 vptrs。每一个类会维护自己的 vtbls 和 vptrs,派生类不仅有自己的 vtbls 内容,还包含基类的 vtbls 内容。当继承关系变得复杂时,vptrs 会使对象的内存空间大量增加。另外,使用虚函数,inline 便会失效,因为在编译阶段无法知道要调用哪一个具体函数。