共计 3911 个字符,预计需要花费 10 分钟才能阅读完成。
效率
条款 16:谨记 80-20 法令
- 80-20 法令说:一个程序 80% 的资源用于 20% 的代码身上。是的。80% 的执行工夫花在大概 20% 的代码身上,80% 的内存被大概 20% 的代码应用,80% 的磁盘拜访动作由 20% 的代码执行,80% 的保护力量花在 20% 的代码下面。
- 不用拘泥于法令的数字,其根本重点是:软件的整体性能简直总是由其形成因素(代码)的一小部分决定。
- 从某个角度看,80-20 法令暗示,大部分时候你所产出的代码,其性能坦白说是平庸的,因为 80% 的工夫中,其效率不会影响零碎整体性能。或者这不至于对你的自尊心造成太大打击,但应该多少会升高你的压力。从另一个角度看,这个法令暗示,如果你的软件有性能上的问题,你将面临悲惨的前景,因为你不只须要找出造成问题的那一小段瓶颈所在,还必须找出方法来大幅晋升其性能。这些工作中,最麻烦的还是找出瓶颈所在。
- 找性能瓶颈的办法不能靠猜。可行之道是齐全依据察看或试验来辨认出造成你心痛的那 20% 代码。而辨识之道就是借助某个程序分析器。然而并不是任何分析器都足堪大任,它必须能够间接测量你所在意的资源。
条款 17:思考应用 lazy evaluation(缓式评估)
- 一旦你采纳 lazy evaluation,就是以某种形式撰写的你 classes,使它们延缓运算,直到那些运算后果迫不及待地被迫切需要为止。如果其估算后果始终不被须要,运算也就始终不执行。
- lazy evaluation 在许多畛域中都可能有用处:可防止非必要的对象复制,可区别 operator[] 的读取和写动作,可防止非必要的数据库读取动作,可防止非必要的数值计算动作。
条款 18:分期摊还预期的计算成本
- 此条款背地的哲学可称为超急评估(over-eager evaluation):在被要求之前就先把事件做上来。
- over-eager evaluation 背地的观点是,如果你预期程序经常会用到某个计算,你能够升高每次计算的均匀老本,方法就是设计一份数据结构以便可能极有效率地解决需要。
- 其中个最简略的一个做法就是将“曾经计算好而有可能再被须要”的数值保留下来(所谓 caching)。另一种做法则是事后取出(prefetching)。prefetching 的一个典型例子就是 std 的 vector 数组扩张的内存调配策略(每次扩充两倍)。
- 条款 17 和 18 看似矛盾,理论都体现了计算机中一个古老的思维:用空间换工夫。联合起来看,它们是不矛盾的。当你必须反对某些运算而其后果并不总是须要的时候,lazy evaluation 能够改善程序效率。当你必须反对某些运算而其后果简直总是被须要,或其后果经常被屡次须要的时候,over-eager evaluation 能够改善程序效率。
条款 19:理解长期对象的起源
- C++ 真正的所谓的长期对象时不可见的——不会在你的源代码中呈现。只有你产生一个 non-heap object 而没有为它命名,便诞生了一个长期对象。此等匿名对象通常产生于两种状况:一是当隐式类型转换被实施起来以求函数调用可能胜利;二是当函数返回对象的时候。
- 任何时候只有你看到一个 reference-to-const 参数,就极可能会有一个长期对象被产生进去绑定至该参数上。任何时候只有你看到函数返回一个对象,就会产生长期对象(并于稍后销毁)。学些找出这些架构,你对幕后老本(编译器行为)的洞察力将会有显著地晋升。
条款 20:帮助实现“返回值优化(RVO)”
- 如果函数肯定得以 by-value 形式返回对象,你相对无奈打消之。从效率的眼光来看,你不应该在乎函数返回了一个对象,你应该在乎的是那个对象的老本几何。你须要做的,是致力找出某种办法以升高被返回对象的老本,而不是想尽办法打消对象自身。
- 咱们能够用某种非凡写法来撰写函数,使它在返回对象时,可能让编译器打消长期对象的老本。咱们的手腕是:返回所谓的 constructor arguments 以取代对象。思考分数(rational numbers)的 operator* 函数,一个有效率而且正确的做法是:
const Rational operator*(const Rational& lhs, const Rational& rhs)
{return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
- C++ 容许编译器将长期对象优化,使它们不存在。于是如果你这样调用 operator*:
Rational a = 10;
Rational b(1,2);
Rational c = a * b;
- 你的编译器得以打消“operator* 内的长期对象”及“被 operator* 返回的长期对象”。它们能够将 return 表达式所定义的对象结构于 c 的内存内。
- 你能够将此函数申明为 inline,以打消调用 operator* 所破费的额定开销。这就是返回一个对象最有效率的做法。
条款 21:利用重载技术(overload)防止隐式类型转换(implicit type conversions)
- 思考以下代码:
class UPInt { // 这个 class 用于有限精细的整数
plublic:
UPInt();
UPInt(int value);
...
};
const UPInt operator+(const UPInt& lhs, const UPInt& rhs);
UPInt upi1, upi2;
...
UPInt upi3 = upi1 + upi2;
upi3 = upi1 + 10;
upi3 = 10 + upi2;
- 因为有隐式转换,所以以上语句都能执行胜利。但这样执行会产生长期对象,这对执行效率是有影响的。
- 如果有其余做法能够让 operator+ 在自变量类型混淆的状况下呗调用胜利,那便打消了了类型学转换的需要。如果咱们心愿可能对 UPInt 和 int 进行加法,咱们须要做的就是将咱们的用意通知编译器,做法是申明数个函数,每个函数有不同的参数表:
const UPInt operator+(const UPInt& lhs, const UPInt& rhs); // 将 UPInt 和 UPInt 相加
const UPInt operator+(const UPInt& lhs, const int& rhs); // 将 UPInt 和 int 相加
const UPInt operator+(const int& lhs, const UPInt& rhs); // 将 int 和 UPInt 相加
- 然而不要写出以下函数:
const UPInt operator+(const int& lhs, const int& rhs);
- 因为,C++ 存在很多游戏规则,其中之一就是:每个“重载操作符”必须取得至多一个“用户定制类型”的自变量。int 不是用户定制类型,所以咱们不可能将一个只取得 int 自变量的操作符加以重载,这会扭转 ints 的加法意义。
条款 22:思考以操作符复合模式(op=)取代其单身模式(op)
- 要确保操作符的复合模式(例如,operator+=)和其单身模式(例如,operator+)之间的天然关系可能存在,一个好办法就是以前者为根底实现后者:
const Rational operator+(const Rational& lhs, const Rational& rhs)
{return Rational(lhs) += rhs;
}
- 一般而言,复合操作符比其对应的单身版本效率高,因为单身版本通常必须返回一个新对象,而咱们必须因而累赘一个长期对象的结构和析形成本。至于复合版本则是间接将后果写入其左端自变量,所以不须要产生一个长期对象来搁置返回值。
- 操作符我复合版本比其对应的单身版本有着更高效率的偏向。身为一位程序库设计者,你应该两者都提供;身为一位应用软件开发者,如果性能是重要因素的话,你应该思考以复合版本操作符取代其单身版本。
条款 23:思考应用其余程序库
- 不同的设计者面对不同的个性会给予不同的优先权。他们的设计各有不同的就义。于是,很容易呈现“两个程序库提供相似机能,却又相当不同的性能体现”的状况。
- 所以一旦你找出程序的性能瓶颈(通过分析器),你应该思考是否有可能因为改用另一个程序库而移除了那些瓶颈。
条款 24:理解 virtual functions、multiple inheritance、virtual base classes、runtime type identification 的老本
- 虚函数的第一个老本:你必须为每个领有虚函数的 class 消耗一个 vtbl 空间,其大小视虚函数的个数(包含继承而来的)而定。
- 虚函数的第二个老本:你必须在每一个领有虚函数的对象内付出“一个额定指针”的代价。
- inline 意味着在编译期将调用端的调用动作被调用函数的函数本体取代,而 virtual 则意味着直到运行期才直到哪个函数被调用。因而虚函数的第三个老本就是:你事实上等于放弃了 inlining。
- 多重继承往往导致 virtual base classes(虚构基类)的需要。virtual base classes 可能导致对象内的暗藏指针减少。
- RTTI 让咱们得以在运行期取得 objects 和 classes 的相干信息,它们本寄存在类型为 type_info 的对象内。你能够利用 typeid 操作符获得某个 class 相应的 type_info 对象。
- RTTI 的设计理念是:依据 class 的 vtbl 来实现。举个例子,vtbl 数组中,索引为 0 的条目可能内含一个指针,指向“该 vtbl 所对应的 class”的相应的 type_info 对象。使用这种实现办法,RTTI 的空间老本就只需在每一个 class vtbl 内减少一个条目,再加上每个 class 所需的一份 type_info 对象空间。
正文完