More Effective C++之效率
副标题#e# 我猜疑一些人在C++软件开拓人员身长举办奥秘的巴甫洛夫试验,不然为什么当提到“效率”这个词时,很多措施员城市流口水。(Scott Meyers真诙谐 译者注)
事实上,效率可不是一个恶作剧的工作。一个太大或太慢的措施它们的利益无论何等引人注目都不会为人们所接管。原来就应该这样。软件是用来辅佐我们更好地事情,说运行速度慢才是更好的,说需要32MB内存的措施比仅仅需要16MB内存的措施好,说占用100MB磁盘空间的措施比仅仅占用50MB磁盘空间的措施好,这的确是无稽之谈。并且尽量有一些措施确是为了举办更巨大的运算才占用更多的时间和空间,可是对付很多措施来说只能归罪于其糟糕的设计和草率的编程。
在用C++写出高效地措施之前,必需认识到C++自己绝对与你所碰着的任何机能上的问题无关。假如想写出一个高效的C++措施,你必需首先能写出一个高效的措施。太多的开拓人员都忽视了这个简朴的原理。是的,轮回可以或许被手工展开,移位操纵(shift operation)可以或许替换乘法,可是假如你所利用的高层算法其内涵效率很低,这些微调就不会有任何浸染。当线性算法可用时你是否还用二次方程式算法?你是否一遍又一各处计较反复的数值?假如是的话,可以绝不浮夸地把你的措施比喻成一个二流的参观胜地,即假如你有特另外时间,才值得去看一看。
本章的内容从两个角度叙述效率的问题。第一是从语言独立的角度,存眷那些你能在任何语言里都能利用的对象。C++为它们提供了出格吸引人的实现途径,因为它对封装的支持很是好,从而可以或许用更好的算法与数据布局来替代低效的类实现,同时接口可以保持稳定。
第二是存眷C++语言自己。高机能的算法与数据布局固然很是好,但假如实际编程中代码实现得很粗拙,效率也会低落得相当多。潜在危害性最大的错误是既容易犯又不容易察觉的错误,濒繁地结构和释放大量的工具就是一种这样的错误。过多的工具结构和工具释放对付你的措施机能来说就象是在大出血,在每次成立和释放不需要的工具的进程中,名贵的时间就这么流走了。这个问题在C++措施中很普遍,我将用四个条款来说明这些工具从那边来的,在不影响措施代码正确性的基本上如何消除它们。
#p#副标题#e#
成立大量的工具不会使措施变大而只会使其运行速度变慢。尚有其它一些影响机能提高的因素,包罗措施库的选择和语言特性的实现(implementations of language features)。在下面的条款中我也将涉及。
在进修了本章内容今后,你将熟悉可以或许提高措施机能的几个原则,这些原则可以合用于你所写的任何措施。你将知道如何精确地防备在你的软件里呈现不需要的工具,而且对编译器生成可执行代码的行为有着敏锐的感受。
俗话说有备无患(forewarned is forearmed)。所以把下面的内容想成是战斗前的筹备。
紧记80-20准则(80-20 rule)
80-20准则说的是约莫20%的代码利用了80%的措施资源;约莫20%的代码耗用了约莫80%的运行时间;约莫20%的代码利用了80%的内存;约莫20%的代码执行80%的磁盘会见;80%的维护投入于约莫20%的代码上;通过无数台呆板、操纵系统和应用措施上的尝试这条准则已经被再三地验证过。80-20准则不可是一条好记的习用语,它更是一条有干系统机能的指导目的,它有着遍及的合用性和坚硬的尝试基本。
当想到80-20准则时,不要在详细数字上胶葛不清,一些人喜欢更严格的90-10准则,并且也有一些试验证据支持它。不管精确地数字是几多,根基的概念是一样的:软件整体的机能取决于代码构成中的一小部门。
当措施员力图最大化晋升软件的机能时,80-20准则既简化了你的事情又使你的事情变得巨大。一方面80-20准则暗示大大都时间你可以或许编写机能一般的代码,因为80%的时间里这些代码的效率不会影响到整个系统的机能,这会淘汰一些你的事情压力。而另一方面这条准则也暗示假如你的软件呈现了机能问题,你将面对一个坚苦的事情,因为你不只必需找到导致问题的那一小块代码的位置,还必需寻找要领提高它们的机能。这些任务中最坚苦的一般是找到系统瓶颈。根基上有两个差异的要领用来寻找:大大都人用的要领和正确的要领。
大大都人寻找瓶颈的要领就是猜。通过履历、直觉、算命纸牌、显灵板、据说可能其它更荒诞的对象,一个又一个措施员一本正经地宣称措施的机能问题已被找到,因为网络的延迟,不正确的内存分派,编译器没有举办足够的优化可能一些蠢人主管拒绝在要害的轮回里利用汇编语句。这些评估老是以一种带有讥笑的盛气凌人的架式宣布出来,凡是这些讥笑者和他们的预言都是错误的。
#p#分页标题#e#
大大都措施员在他们措施机能特征上的直觉都是错误的,因为措施机能特征往往不能靠直觉来确定。功效为提高措施各部门的效率而倾注了大量的精神,可是对措施的整体行为没有显著的影响。譬喻在措施里插手可以或许最小化计较劲的怪异算法和数据布局,可是假如措施的机能限制主要在I/O上(I/O-bound)那么就丝毫起不到浸染。回收I/O机能强劲的措施库取代编译器自己附加的措施库(拜见条款23),假如措施的机能瓶颈主要在CPU上(CPU-bound),这种要领也不会起什么浸染。
在这种环境下,面临运行速度迟钝或占用过多内存的措施,你该如何做呢?80-20准则的寄义是胡乱地提高一部门措施的效率不行能有很大辅佐。措施机能特征往往不能靠直觉确定,这个事实意味着试图猜出机能瓶颈不行能比胡乱地提高一部门措施的效率这种要领好到那边去。那么会后什么功效呢?
功效是用履历识别措施20%的部门只会导致你心痛。正确的要领是用profiler措施识别出令人讨厌的措施的20%部门。不是所有的事情都让profiler去做。你想让它去直接地丈量你感乐趣的资源。譬喻假如措施太迟钝,你想让profiler汇报你措施的各个部门都淹灭了几多时间。然后你存眷那些局部效率可以或许被极大提高的处所,这也将会很大地提高整体的效率。
profiler汇报你每条语句执行了几多次或各函数被挪用了几多次,这是一个浸染有限的东西。从提高机能的概念来看,你不消体贴一条语句或一个函数被挪用了几多次。究竟很少碰着用户或措施库的挪用者诉苦执行了太多的语句或挪用了太多的函数。假如软件足够快,没有人体贴有几多语句被执行,假如措施运行过慢,不会有人体贴语句有何等的少。他们所体贴的是他们厌恶期待,假如你的措施让他们期待,他们也会厌恶你。
不外知道语句执行或函数挪用的频繁水平,有时能辅佐你洞察软件内部的行为。譬喻假如你成立了100个某种范例的工具,会发明你挪用该类的结构函数有上千次,这个信息无疑是有代价的。并且语句和函数的挪用次数能间接地辅佐你领略不能直接丈量的软件行为。譬喻假如你不能直接丈量动态内存的利用,知道内存分派函数和内存释函数的挪用频率也是有辅佐的。(也就是,operators new, new[], delete, and delete[]—拜见条款8)
虽然纵然最好的profiler也是受其处理惩罚的数据所影响。假如用缺乏代表性的数据profile你的措施,你就不能诉苦profiler会导致你优化措施的那80%的部门,从而不会对措施凡是的机能有什么影响。记着profiler仅可以或许汇报你在某一次运行(或某屡次运行)时一个措施运行环境,所以假如你用不具有代表性的输入数据profile一个措施,那你所举办的profile也没有代表型。相反这样做很大概导致你去优化不常用的软件行为,而在软件的常用规模,则对软件整体的效率起相反浸染(即效率下降)。
防备这种不正确的功效,最好的要领是用尽大概多的数据profile你的软件。另外,你必需确保每组数据在客户(或至少是最重要的客户)如何利用软件的方面能有代表性。凡是获取有代表性的数据是很容易的,因为很多客户都愿意让你用他们的数据举办profile。究竟你是为了他们需求而优化软件。