作为(可能的)示例,LLVM编码标准禁止使用标准RTTI或例外: http://llvm.org/docs/CodingStandards.html#do-not-use-rtti-or-exceptions
这是一个好主意,还是大多数程序的编码标准过时或不合理?
C ++中是否有其他此类功能,即使您不使用它们,也会显着恶化程序的速度,内存使用或可执行文件大小?
答案 0 :(得分:10)
这仍然是一个好主意,还是编码标准过时了?
RTTI绝对是最臭名昭着的零开销原则的违反,因为它产生的静态成本(可执行大小和初始化代码)与多态类的数量成比例(即,至少有一个类的类)虚拟功能),它不依赖于你使用它多少,如果有的话。但是如果没有一些每类开销,就没有办法真正提供RTTI。这就是为什么你可以在大多数编译器上禁用RTTI的原因,如果你根本不需要它,或者如果你想用一个你有更多控制权的RTTI系统替换它(就像LLVM人员那样)。然而,如果你启用了RTTI,并且你没有使用它,那么开销只是代码膨胀(更大的可执行文件,更大的内存空间,更大的代码扩展)和加载/卸载时间的形式,因此,运行时间执行开销几乎不存在。但是在资源匮乏的环境中,或者对于小型实用程序(例如,在shell脚本中重复调用),静态开销可能无法承受。此外,在许多实际需要高性能RTTI的情况下,大多数情况下您根本不需要它,而在其他时候您需要在一些特殊的地方使用它。与其他事物相比,通常不具备性能要求。 LLVM在这里是一个例外,因为编写编译器涉及处理抽象语法树和类似的描述性类层次结构,如果没有大量的向下转换很难做到这一点,并且因为分析这些结构是编译器所做的核心操作,性能下降-casts(或其他RTTI调用)至关重要。因此,请不要使用RTTI"不要使用RTTI"作为一般规则,只需了解它所带来的开销,并知道您的应用程序在成本/收益方面是否可以接受。
C ++异常肯定是下一个可能比你准备讨价还价更多开销的东西。这是一个更具争议性的问题,特别是在实际表征整体异常的开销时。根据经验评估异常的开销是一项非常困难的任务,因为它高度依赖于使用模式,即,有不同的方法来使用异常,不同的严重程度(您是否使用异常来处理错误,致命错误,异常情况,或者替换每个if语句?),并且对错误处理有不同程度的关注(无论是否使用异常)。当然,不同的编译器可以不同地实现异常。当前的实现,即所谓的零成本异常",是为了在正常执行期间实现零运行时成本,但这会留下相当多的静态开销,并使得throw-to-catch执行路径慢点。 Here是一个很好的概述。如果例外情况违反了"您只需支付使用的费用"原则,这是真的(除非你禁用它们),但它们通常是合理的。有例外的基本假设是,作为程序员,您打算编写健壮的错误处理代码,并且如果您确实适当地处理了所有错误,那么与错误处理代码(catch-blocks)相比,异常的开销将变得苍白。和destructors),你可能会有一个比相同数量的错误处理的等效C风格的错误代码实现更小更快的程序。但是如果你不打算做很多错误处理(例如,如果出现任何问题,只是崩溃!"方法),那么异常会产生很大的开销。我不确定为什么LLVM会禁止异常(如果我不得不直言不讳,我会说它是因为他们对错误处理并不是很认真,据我所知我从该项目看到的代码)。所以,长话短说,指南应该是"如果你打算认真处理错误,使用例外,否则,不要"。但请记住,这是一个备受争议的话题。
是否有任何其他此类功能违反"您只需支付使用的费用"?
你已经命名为两个显而易见的,并且毫不奇怪,它们是大多数编译器具有禁用选项的两个主要功能(并且在适当时通常被禁用)。当然还有其他更微小的违反零开销原则的行为。
一个臭名昭着的例子是标准库中的IO流库(<iostream>
)。这个库经常被批评为大多数人需要和使用它的开销过多。 IO流库倾向于引入大量代码,并且需要相当多的加载时初始化。然后,它的许多类如std::ostream
或std::ofstream
都有太多的运行时开销,无论是构造/破坏还是读/写性能。我认为它们在该库中包含了太多的功能,并且由于大多数时候IO流传输任务非常简单,因此这些功能通常不被使用且开销不合理。但一般来说,我发现开销通常是可以接受的,特别是因为大多数IO任务已经很慢了。顺便说一句,LLVM也禁止使用IO流库,这也是因为LLVM的目标是编写执行大量文件IO的精简和平均命令行实用程序(如编译器或其相关工具)
可能有其他标准库的开销超过某些特定情况下可能希望拥有的开销。图书馆代码通常必须做出让所有人都不满意的妥协。我怀疑一些较新的库,如线程,计时,正则表达式和随机,提供了比许多应用程序所需的更多的功能或强大的保证,因此,会带来一些不必要的开销。但话说回来,许多应用程序确实从这些功能中受益。这就是妥协的意义。
对于过度开销的语言规则,有许多小问题会产生一些开销。首先,我可以想到标准必须做出一些阻止优化的保守假设的几个地方。一个值得注意的例子是无法restrict指针别名,这迫使编译器假设任何内存都可能被任何指针别名化(即使实际上,指针别名很少),这限制了优化的机会。有许多类似的情况,编译器必须使&#34;安全&#34;假设,限制优化。但是大多数这些都在范围和潜在的好处相当小,并且它们通常在能够保证行为的正确性(以及可重复性,稳健性,可移植性等)方面是合理的。另外,请注意,在绝大多数情况下,它在其他语言中并没有变得更好,在C中可能稍微好一些,但这是关于它的。其中一些问题也可以通过编译器扩展或特定于平台的功能来规避,或作为最后的手段,使用内联汇编代码,也就是说,如果您确实需要优化到该级别。
一个不再有效的例子是要求编译器生成异常处理(堆栈展开)代码的问题,即使对于永不抛出的函数也是如此。现在,可以通过在相关函数上指定noexcept
来解决这个问题。
但除了那些微观问题之外,我无法真正想到C ++中任何其他主要的过度开销(除了RTTI和例外)。我的意思是,C ++中有些东西可以创造不同种类的开销,但它们都是“选择加入”#34;功能(按用途),如虚拟功能,虚拟继承,多重继承,模板等...但那些大多遵守&#34;你只需支付你使用的东西&#34;原理。有关为C ++的低开销子集强加的规则的示例,请查看Embedded C++。