一般C ++性能改进技巧

时间:2010-01-08 19:37:13

标签: c++

有人可以指点我一篇文章,或者在这里写一些关于一些C ++编程习惯的技巧,这些习惯通常是有效的(没有真正的缺点)并且可以提高性能吗?我不是指编程模式和算法的复杂性 - 我需要一些小的东西,比如你如何定义你的函数,要做的事情/要避免在循环中,在堆栈上分配什么,堆上的内容等等。

这不是关于如何更快地制作一个特定的软件,也不是关于如何创建一个干净的软件设计,而是关于编程习惯 - 如果你总是应用它们,你将使你的代码比一点点快一点慢。

24 个答案:

答案 0 :(得分:32)

Effective C++More Effective C++Effective STLC++ Coding Standards中的一些提示就在这一行。

此类提示的一个简单示例:尽可能使用preincrement(++ i)而不是postincrement(i ++)。这对于迭代器尤为重要,因为后增量涉及复制迭代器。优化器可以能够撤销这个,但是编写预增量不是额外的工作,那么为什么要冒风险呢?

答案 1 :(得分:26)

如果我理解正确,你会问到避免过早的悲观化,这是避免过早优化的一个很好的补充。根据我的经验,要避免的首要任务是尽可能不复制大型对象。这包括:

  • 通过(const)引用将对象传递给函数
  • 只要实际可以通过(const)引用返回对象
  • 确保在需要时声明引用变量

这最后一个子弹需要一些解释。我不能告诉你我见过多少次:

class Foo
{
    const BigObject & bar();
};

// ... somewhere in code ...
BigObject obj = foo.bar();  // OOPS!  This creates a copy!

正确的方法是:

const BigOject &obj = foo.bar();  // does not create a copy

这些指南适用于大于智能指针或内置类型的任何内容。另外,我强烈建议您花时间学习分析您的代码。一个好的分析工具将有助于捕获浪费的操作。

答案 2 :(得分:16)

我的一些小小的烦恼:

  1. 在使用/初始化之前不要声明(实际上,定义)对象变量(如在C中)。这需要构造函数和分配操作符函数运行,对于复杂对象,可能会很昂贵。
  2. 首选预增量到后增量。这仅适用于具有重载运算符的迭代器和用户定义类型。
  3. 尽可能使用最小的基元类型。不要使用long int来存储0..5范围内的值。这将减少总体内存使用量,改善位置,从而降低整体性能。
  4. 仅在必要时使用堆内存(动态分配)。许多C ++程序员默认使用堆。动态分配和解除分配是很昂贵的。
  5. 尽量减少临时使用(特别是基于字符串的处理)。 Stroustrup提供了一种很好的技术,用于在“C ++编程语言”中定义三元和更高阶算术运算符的逻辑等价。
  6. 了解您的编译器/链接器选项。还要知道哪些导致非标准行为。这些会显着影响运行时性能。
  7. 了解STL容器的性能/功能权衡(例如,不要频繁插入向量,使用列表)。
  8. 当无条件地分配变量,对象,容器等时,不要初始化它们。
  9. 考虑复合条件的评估顺序。例如,给定if (a && b),如果b更可能是假的,则将其放在第一位以保存对a的评估。
  10. 还有许多其他“坏习惯”,我不会提及,因为在实践中,现代编译器/优化器将消除不良影响(例如,返回值优化与传递引用,循环展开等)。

答案 3 :(得分:15)

其中一个好的起点是Sutter's Guru of the week系列,以及 Exceptional C ++ 这些书籍。

答案 4 :(得分:11)

Optimizing Software in C++Agner Fog”通常是优化技术的最佳参考之一,既简单又绝对更高级。另一个很大的优点是可以在他的网站上免费阅读。 (请参阅他的网站名称中的链接,并链接到pdf的纸质标题)。

编辑另请注意,90%(或更多)的时间花费在代码的10%(或更少)上。因此,一般来说优化代码实际上是针对您的瓶颈。此外,了解现代编译器将比大多数编码器更好地进行优化是非常重要和有用的,尤其是微优化,例如延迟变量的初始化等。编译器通常非常擅长优化,因此花时间编写稳定,可靠的和简单的代码。

我认为,至少在大多数情况下,更多地关注算法的选择而不是微观优化是值得的。

答案 5 :(得分:11)

使用仿函数(实现了operator()的类)而不是函数指针。编译器可以更轻松地内联前者。这就是为什么C ++的std::sort往往比C qsort表现得更好(当给出一个仿函数时)。

答案 6 :(得分:8)

从你的问题看来,你已经知道“过早优化是邪恶的”哲学,所以我不会那么鼓吹。 :)

现代编译器已经非常聪明地为您进行微优化。如果你太努力了,你通常可以比原来的直接代码慢。

对于小的“优化”,你可以不假思索地安全地做,并且不会对代码的可读性/可维护性产生太大影响,请查看本书 C ++编码标准由Sutter& Alexandrescu的。

有关更多优化技巧,请查看Bulka& amp;的 Efficient C ++ 。梅休。仅在通过分析证明合理时使用!

要获得良好的通用C ++编程实践,请查看:

    Sutter& Sons的
  • C ++编码标准 Alexandrescu(必须有,恕我直言)
  • Scott Meyers的有效C ++ / STL系列
  • Herb Sutter
  • Exceptional C ++ 系列

在我的头脑中,一个很好的一般性能练习是通过引用传递重量级对象,而不是通过复制。例如:

// Not a good idea, a whole other temporary copy of the (potentially big) vector will be created.
int sum(std::vector<int> v)
{
   // sum all values of v
   return sum;
}

// Better, vector is passed by constant reference
int sum(const std::vector<int>& v)
{
   // v is immutable ("read-only") in this context
   // sum all values of v.
   return sum;
}

对于像复数或二维(x,y)点这样的小对象,该函数可能会在副本传递的对象下运行得更快。

对于固定大小的中等重量对象,如果使用副本或对象的引用,函数运行得更快,则不太清楚。只有剖析才能说明问题。我通常只是通过const引用传递(如果函数不需要本地副本),只有在分析告诉我时才会担心它。

有些人会说你可以不假思索地内联小类方法。这可能会提高运行时性能,但如果有大量内联,它也可能会延长编译时间。如果类方法是库API的一部分,那么最好不要内联它,无论它有多小。这是因为内联函数的实现必须对其他模块/类可见。如果您在内联函数/方法中更改了某些内容,则需要重新编译引用它的其他模块。

当我第一次开始编程时,我会尝试微观优化一切(那是我的电气工程师)。真是浪费时间!

如果您使用的是嵌入式系统,那么事情会发生变化,您无法将记忆视为理所当然。但这是另一整套蠕虫。

答案 7 :(得分:5)

使用正确的容器

序列容器

  • 如果您要继续向其中添加数据,请使用vector来查看未知大小的数据。如果您要反复拨打push_back(),请使用reserve()或使用deque代替。
  • 如果您要在容器中间添加/删除数据,list可能是正确的选择。
  • 如果您要从容器的两端添加/删除数据,deque可能是正确的选择。
  • 如果您需要访问容器的第n个元素,list可能是错误的选择。
  • 如果您需要同时访问容器的第n个元素并在中间添加/删除元素,请对所有三个容器进行基准测试。
  • 如果您具有C ++ 0x功能并使用list,但您从未在列表中向后移动,那么您可能会发现forward_list更符合您的喜好。它不会更快,但会占用更少的空间。

请注意,此建议越适用于容器越大。对于较小的容器,vector可能总是正确的选择,因为较低的常数因素。如有疑问,请以基准为准。

关联容器

  • 如果您没有TR1,C ++ 0x或特定于供应商的unordered_foo / hash_foo,则没有多少选择。使用四个容器中的任何一个都适合您的需要。
  • 如果你有一个unordered_foo,如果你不关心元素的顺序,你可以使用它而不是有序的版本,并且你有一个很好的哈希函数。

明智地使用例外

  • 不要在正常的代码路径中使用异常。当你确实遇到异常情况时,请保存它们。

爱模板

  • 模板将在编译时和空间方面花费您的成本,但如果您有在运行时执行的计算,性能提升可能会很惊人;有时候甚至会像堕落者那样微妙。

避免dynamic_cast

  • dynamic_cast有时是做某事的唯一选择,但通常可以通过改进设计来消除dynamic_cast的使用。
  • 请勿将dynamic_cast替换为typeid,后跟static_cast

答案 8 :(得分:5)

我喜欢这个问题,因为它要求一些“好习惯”。 我发现编程中可取的某些东西最初是一件苦差事,但一旦成为习惯就变得可以接受甚至变得容易。

一个例子是始终使用智能指针而不是原始指针来控制堆内存生存期。当然,另一个相关的问题是养成了一直使用RAII进行资源获取和发布的习惯。另一个是始终使用异常进行错误处理。 这三种方法倾向于简化代码,从而使代码更小,更快,更容易理解。

你也可以隐式内联getter和setter;总是充分利用构造函数中的初始化列表;并始终使用std库中提供的find和其他相关函数,而不是制作自己的循环。

不是特别是C ++,但通常值得避免数据复制。在具有大量内存分配的长时间运行的程序中,将内存分配视为设计的主要部分是值得的,因此您使用的内存来自重用的池,尽管这不一定是常见的事情。被认为值得养成习惯。

还有一件事 - 如果您需要功能,请不要将代码从一个地方复制到另一个地方 - 使用一个功能。这样可以减小代码大小,并且可以更轻松地优化使用此功能的所有场所。

答案 9 :(得分:5)

以下是关于此主题的精彩文章:How To Go Slow

答案 10 :(得分:4)

除非您确定其他容器类型更好,否则请使用'std :: vector'。即使'std :: deque,'std :: list','std :: map'等似乎是更加方便的选择,一个向量在内存使用和元素访问\迭代次数上都超过它们。

此外,更喜欢使用容器成员算法(即'map.equal_range(...)')而不是全局对应物('std :: equal_range(begin(),end()...)')< / p>

答案 11 :(得分:4)

模板!使用模板可以减少代码量,因为您可以使用可以使用多种数据类型重用的类或函数/方法。

请考虑以下事项:

#include <string>
using std::basic_string;

template <class T>
    void CreateString(basic_string<T> s)
    {
        //...
    }

basic_string可以由char,wchar_t,unsigned char或unsigned wchar_t组成。

模板也可以用于一系列不同的东西,比如traits,class specialization,甚至用于将int值传递给类!

答案 12 :(得分:3)

避免尽可能多次迭代同一数据集。

答案 13 :(得分:2)

This page sum up all you have to know about optimization in C++ (be it while or after writing software).这是非常好的建议并且非常有用 - 并且可以在项目的优化阶段用作有用的提醒。

它有点旧,所以你也必须知道你的编译器已经完成了优化(比如NRVO)。

除此之外,阅读已经被引用的Effective C ++,More Effective C ++,Effective STL和C ++ Coding Standards也很重要,因为它解释了很多关于语言和STL中发生的事情,允许通过更好地了解正在发生的事情,您可以更好地优化您的具体案例。

答案 14 :(得分:2)

我建议你阅读Jon Bentley的"Programming Pearls"第二章(“表演”)。它不是特定于C ++的,但这些技术也可以在C或C ++中应用。该网站仅包含本书的部分内容,我建议您阅读本书。

答案 15 :(得分:2)

我习惯于更喜欢写++i而不是i++,而不是当iint时它会带来任何性能提升但i时情况有所不同是一个iterator,可能有一个复杂的实现。

然后假设您来自C编程语言,失去了在函数开头声明所有变量的习惯:在函数流中需要时声明变量,因为函数可能包含早期{{1}在一些在开头初始化的变量被有效地使用之前的语句。

除此之外,另一个资源是Herb Sutter(他再次)和Alexei Alexandrescu的C++ Coding Standards: 101 Rules, Guidelines, and Best Practices

还有最新版本的Scott Meyers的Effective C ++:Effective C++: 55 specific ways to improve your programs and designs

最后,我想提一下托尼·阿尔布雷希特的Pitfalls of Object Oriented Programming演讲:并不是说它包含了你可以盲目追随的经验法则,但这是一个非常有趣的读物。

答案 16 :(得分:2)

这是我过去提到过的一个列表 - http://www.devx.com/cplus/Article/16328/0/page/1。除此之外,谷歌搜索c ++性能提示产生了很多。

答案 17 :(得分:1)

  1. 避免内存碎片。
  2. 对齐记忆。
  3. SIMD说明。
  4. 无锁多线程。
  5. 使用适当的加速树,例如kd-tree,cover tree,octree,quadtree等。 5A。以允许前三个的方式定义这些(即将节点全部放在一个块中)
  6. 内联。最低悬挂但非常美味的水果。
  7. 你可以通过这种方式获得的性能提升是令人惊讶的。对于我来说,计算量很大的应用程序1500次。不是粗暴的,而是在主要软件包中编写的类似数据结构。

    我不会因为事后的影响而感到困扰。这只能节省某些(不重要)的情况,并且提到的大部分内容都是类似的东西,可能偶尔会在这里和那里刮掉额外的1%,但通常不值得打扰。

答案 18 :(得分:1)

这个将具有非常好的通用c ++优化技术:

http://www.tantalon.com/pete/cppopt/main.htm

使用分析器查看应用程序的哪个部分运行缓慢,然后使用优化技术。

我使用valgrind和callgrind工具进行分析,它会给你哪些行花费多少。

valgrind --tool = callgrind

答案 19 :(得分:1)

这里有很多好的建议。

养成良好习惯的最佳方法之一就是强迫自己。为此,我喜欢PC-Lint。 PC-Lint实际上将强制执行Scott Meyer的Effective C ++&amp;更有效的C ++规则。遵守Lint规则往往会使维护更容易,更容易出错,并且代码更清晰。当你意识到lint通常会产生比源代码更多的输出时,不要太疯狂;我曾经参与过一个包含150MB源代码和1.8GB Lint消息的项目。

答案 20 :(得分:0)

为什么到目前为止没人提到它?为什么每个人都变得很穷++i

您可以轻松完成的最好的小事之一,不要认真您的代码:

有效的C ++ 作者:Scott Meyers ,第20项:

首选将参考传递给const传递值

示例:

// this is a better option
void some_function(const std::string &str);
// than this:
void some_function(std::string str);

如果短std::string你可能不会赢得太多,但传递这样的对象,可以为你节省大量的计算能力,因为你可以避免冗余复制。如果您忘记实施复制构造函数,也可以将您从一两个错误中解救出来。

答案 21 :(得分:0)

  • 避免多重继承。
  • 在必要时使用虚拟广告,而不仅仅是为了好玩
  • 仅在不痛苦的情况下使用模板化集合类

答案 22 :(得分:0)

提高这些技能的最佳方法是阅读书籍和文章,但我可以帮助您提供一些提示:

  • 1-按引用接受对象,按值接受基元或指针类型,但如果函数存储引用或指向对象的指针,则使用对象指针。
  • 2-不要使用MACROS来声明常量 - &gt;使用static const。
  • 3-如果您的类可能是子类,请始终实现虚拟析构函数。

答案 23 :(得分:0)

喜欢使用预增量。

使用int / pointer等没有区别 但是对于类类型,标准的实现方式需要创建一个新对象。

所以更喜欢预增量。以防万一在以后的情况下,类型会发生变化 然后,您无需修改​​代码即可应对。