在编码时,在性能方面要记住什么是一个好的经验法则?有无穷无尽的方法可以针对特定平台和编译器进行优化,但我正在寻找在编译器和平台上同样适用(或几乎)的答案.
脑海中浮现出一句名言:
"我们应该忘记小的效率,比如大约97%的时间:过早的优化是所有邪恶的根源." (Knuth,Donald.结构化编程,参见陈述,ACM期刊计算调查,第6卷,第4期,1974年12月.第268页.)
但也许你不应该按价值传递大数据结构...... :-)
编辑:也许还可以避免使用O(N ^ 2)或更复杂的算法......
数字#1性能提示是尽早和经常分析您的代码.有很多一般的"不要这样做"的提示,但很难保证这会影响你的应用程序的性能.为什么?每个应用程序都不同.如果你有很多元素但你的程序甚至使用了一个向量(你可能应该......),很容易说按值传递向量是不好的?
分析是了解应用程序性能的唯一方法.我遇到过很多情况,人们"优化"了代码但却没有描述过."优化"最终引入了许多错误,甚至不是代码路径中的热点.浪费每个人的时间.
编辑:
有几个人对我答案的"早期"部分发表了评论.我不认为你应该从第1天开始进行剖析.但是你也不应该等到船上1个月.
我通常首先介绍一下我有一些明确的端到端场景,或者在一个更大的项目中,一个主要功能组件.我需要一两天(通常与QA合作)将一些大型场景聚集在一起并将其抛在代码中.这是一个很好的现场检查,以及早发现明显的性能问题.在这一点上修复它们会容易一些.
在一个典型的项目中,我发现我的代码符合此标准的30%-40%通过项目(100%在客户手中).我很早就把这个时间分类了.
可能时使用if
或switch
代替通过函数指针调用.澄清:void doit(int m) { switch(m) { case 1: f1(); break; case 2: f2(); break; } }
而不是void doit(void(*m)()) { m(); }
可以内联调用.
如果可能并且没有造成伤害,则更喜欢CRTP到虚拟功能
如果可能,请避免使用C字符串并使用String类.它会经常更快.(恒定时间长度"度量",附加摊销的常数时间,...)
始终通过引用const(T const&)而不是复制值来传递用户定义的类型值(除了它没有意义的地方.例如迭代器).
对于用户定义的类型,总是更喜欢++t
而不是t++
const
经常使用早期.最重要的是提高可读性.
尽量保持new
最低限度.如果可能的话,总是更喜欢自动变量(在堆栈上)
而不是自己填充数组,更喜欢使用空的初始化列表进行初始化,就像T t[N] = { };
你想要零一样.
尽可能经常使用构造函数初始化列表,尤其是在初始化用户定义的类型成员时.
使用仿函数(具有operator()
重载的类型).它们比通过函数指针调用更好地内联.
不要使用类似的类,std::vector
或者std::string
如果您的数量固定,则不会增长.使用boost::array
或裸阵列并正确使用它.
事实上,我几乎忘了它:
过早优化是万恶之源
有人提到了函数指针(以及为什么要使用它if
).好吧,甚至更好:使用仿函数,它们内联并且通常没有开销.仿函数是一个结构(或类,但通常是前者),它重载运算符()
,其实例可以像普通函数一样使用:
templatestruct add { operator T ()(T const& a, T const& b) const { return a + b; } }; int result = add ()(1, 2);
这些几乎可以在可以使用普通函数或函数指针的每个上下文中使用.他们通常从任一派生std::unary_function
或std::binary_function
但那往往是没有必要(实际上只做过继承了一些有用的typedef
S).
编辑
上述代码中需要显式类型限定.类型推断仅适用于函数调用,而不适用于实例创建.但是,通常可以通过使用make
辅助函数来省略它.这是在STL for pair
s中完成的:
templatepair make_pair(T1 const& first, T2 const& second) { return pair (first, second); } // Implied types: pair pif = make_pair(1, 1.0f);
有人在评论中提到,仿函数有时被称为"functionoids".是的杂交 -但并不完全.实际上,"functor"是"函数对象"的缩写(有些奇怪).函数在概念上是相似的,但是通过使用虚函数来实现(尽管它们有时被同义地使用).例如,一个functionoid可能看起来像这样(以及它必要的接口定义):
templatestruct UnaryFunctionoid { virtual R invoke(T const& value) const = 0; }; struct IsEvenFunction : UnaryFunctionoid { bool invoke(int const& value) const { return value % 2 == 0; } }; // call it, somewhat clumsily: UnaryFunctionoid const& f = IsEvenFunction(); f.invoke(4); // true
当然,由于其虚函数调用,这会失去仿函数所具有的任何性能优势.因此,它在不同的上下文中使用,实际上需要多态(有状态)运行时函数.
C++ FAQ 在这个主题上有更多的话要说.
在需要之前不要打扰优化.要查明是否需要,请查看个人资料.不要猜; 有证据.
此外,算法优化通常比微算法具有更大的影响.使用A-star而不是暴力寻路会更快,就像Bresenham圈子比使用sin/cos更好.当然也有例外,但它们非常(非常)罕见(<0.1%).如果您有一个好的设计,更改算法只会更改代码中的一个模块.简单.
使用已使用和重复使用的现有已审核代码.(示例:STL,boost vs滚动自己的容器和算法)
由于评论而更新:正确使用已使用和重复使用的现有已审核代码.