在C ++中进行良好性能优化的想法

时间:2010-02-03 08:44:23

标签: c++ optimization

好的,过去三天我一直坐在探查器结果面前,通过自动化套件运行各种各样的测试用例。我们的想法是看看是否有任何良好的优化通常可以提高性能。我将在此上下文中限定 good ,如下所示;

  • 有潜力表现 非常好的改进 最后是重要的和可观察的 用户级别,例如> 100%的改善 在一个表现不佳的地区。

  • 具有核心空间的潜力 减少使用量>减少50% 在数据繁重的地区。

  • 易于实施,最小化 对代码进行模糊处理,并且最小化 副作用。即...的好处 大大实施优化 超重成本。

该应用程序是一个3D映射和建模软件包,界面中有大量图形,后端有几何处理。我已经做了很多工作来确保大多数处理的最佳算法选择,在这个阶段我正在寻找任何通用的简单方法来处理大型和复杂的数据集时获得额外的优势。到目前为止,我已经提出以下建议;

  • 搜索时,请保留最近找到的项目的缓冲区并先检查。通过重复搜索进行的大量处理似乎在同一区域内进行搜索。从答案到目前为止,这似乎是memoization

  • 的特定形式
  • 排序时,检查数据是否已按排序顺序排列(特别是在使用qsort的地方)

  • 将GUI和处理保持在单独的线程中(使良好的标准很容易实现,但IMO仍然值得)

  • 如果你有很多构造/破坏时间的本地类变量,那么在使用频繁的成员函数中,要使它们成为私有类成员。尤其是动态数组和字符串,尤其是MFC CArrays和CStrings。

  • 使用动态数组时,请将初始大小设置为略高于典型用量,并采用指数式增长策略。

  • 处理要存储在数组中的非常大的数据集时,首先调整数据大小以避免任何重新分配。

  • 避免在堆栈上创建临时对象副本的函数返回,而是使用引用参数,例如

    CString MyFunc(double x,double y)

效率低于

void  MyFunc(double x, double y, CString &Result)

实际上,在代码的任何性能关键区域中避免使用CStrings和大多数MFC。 (编辑:这可能更普遍地被RVO否定,但不适用于我的应用中的CStrings)

这些项目似乎在我的上下文中运作良好,但有哪些明显的我遗漏了,还是有任何其他优秀的资源优化?

编辑:根据提供的许多评论,显然需要进一步解释。虽然我完全意识到建议对特定代码片段进行特定优化需要查看该代码,但过去几天花在分析分析器输出上已经显示出优化候选者方面的某些模式。我也意识到自己对其他人在这个领域做得很好的事情的无知,并且看到这些技术的列举清单(至少对我而言),无论它们是否适用于我的情况。这个问题不是关于优化的危险,但对于那里的任何初学者,我建议你首先建立一个强烈的需求,在首先考虑之前优化需求。我自己的偏好是根据未来的性能要求在设计阶段进行大多数优化,但我也是分析的强力倡导者,以验证在实施中是否满足设计假设。我会请人们取悦他们自己积极优化的经验,而不是他们的信念 我们是否应该首先考虑优化。

FWIW,在我的自动化套件中,编译器优化代码与否之间的差异为12%,这在最终用户级别是可以边界观察的。

第二次修改:我发现一些相关的帖子非常有用,特别是Mike Dunlavey关于过度依赖于分析器输出的评论。

Performance optimization strategies of last resort?

What can I use to profile C++ code in Linux?

9 个答案:

答案 0 :(得分:9)

  请不要丢掉任何旧的   '优化是万恶之源'   事情,这完全是无关紧要的   问题

是的,然后你有类似的事情:

  

排序时,检查数据是否已按排序顺序

让我想知道你是否使用了有效的算法。其中,“过早优化是万恶之源”的基本前提。

  

并将被投票。

真的?那种语气不好。 IMO。 YMMV。

  

同样,我对此并不感兴趣   让编译器优化的乐趣   它对我来说,或投掷所有内联   在这个地方。 FWIW,区别   在编译器优化之间   代码是否以12%出现   在我的自动化套件中,这是   最终用户可观察到的边界线   水平。

再加上您手工制作的任何优化,您仍然需要进行编译器优化。

除此之外,由于您没有提供关于瓶颈所在位置的具体见解,因此很难提供任何指针。我至少会猜到你:

  • 使用自定义分配器
  • 如果您的目标计算机是向量计算机
  • ,请探索使用向量指令的可能性

编辑:因为你说你不了解RVO:尝试阅读移动语义,特别是这个库:来自Adobe的move library。我想Boost会有类似的东西。

编辑#2:还有两件事:

  • 定点运算可能是一件好事,
  • 尽可能多地使用查找表

答案 1 :(得分:5)

  

CString MyFunc(double x, double y)

     

效率低于

     

void MyFunc(double x, double y, CString &Result)

如果MyFunc写得很干净,那么它们应该是一样的。编译器应该能够利用NRVO。这听起来像你已经描述并发现它不是 - 我只是说它可能更符合你的标准,例如“对代码的最小混淆”,稍微重新排列函数本身以允许NRVO发生。

还有一些事情要尝试:

  1. memoization(类似于你的缓存 反复搜索,但更多 专注于树/图解析, 如果你有任何)。
  2. 如果您不需要额外费用,请使用浮动而不是双打 精确度(如果你的话,甚至可能是整数 能)。
  3. 使用类型来标记假设(您提到了排序数组 - 另一个常见的是小写的 字符串)。创建派生或 包装类型(例如Sorted<T>) 明确地做出这样的假设。那 方法,如果你有一个方法 例如,Sorted<vector<T> > 你给它一个有序的矢量,它 直接通过 - 但如果你 给它一个vector<T>,它必须 构建了一个Sorted<vector<T> >,at 它将对它进行排序。您可以 手动断言它已排序 使用替代构造函数, 但它使携带更容易 你的假设周围也许 抓住你可能拥有的地方 否则错过了。
  4. 不要放弃内联等。确保您完全了解何时 他们应该帮助他们 应该阻碍。在某些情况下他们 真的可以有所作为 - 但是 如果你只是处理,可能不会 他们任意。
  5. 您可能会受益于flyweightpooled object allocators
  6. 穿线时, 尽量减少任何交互 你可以减少代码量 这需要锁定。经常服用 副本甚至相当昂贵 对象可以减少开销 而不是互斥体。明显好好利用 你可以在原子类型中使用。

答案 2 :(得分:3)

我认为你的要求几乎是相互排斥的,除非有某种明显的缺陷(所有的分析都非常适合发现)。

真正改变性能的事情需要付出很多努力,而您的基本数据结构是最重要的。减少内存碎片,对齐内存管理,SIMD,尽可能小的数据结构,尽可能多地分配在一个块中,多线程,从模板中减少代码大小,将参数重新声明为局部变量,以便优化器可以知道它们是相同的优化。在没有大量成本的情况下,最终都无法解决这些问题。

很多时候,您甚至无法轻松衡量真正影响性能的因素,因为它们只会随着程序运行或代码大小的增加而变得昂贵。

答案 3 :(得分:3)

我担心大而复杂的数据结构。我喜欢大而简单的数据结构;特别是数组。当我拥有大而简单的数据结构时,我可以尝试通过内存访问来做一些聪明的事情来真正优化我对缓存的使用;特别是记忆平铺。不确定这对您是否有用,但总的来说,考虑到您的一系列要求和对代码的现有理解,我一直在寻找优化从RAM到CPU的数据获取的方法。

而且,我当然会将地狱与这一切并行化。除非你有一台多台电脑,否则不可能。哦,备忘录,我们这些天都得到了那些!!

祝你好运,让我们知道你的成功。我在SO上阅读了很多关于优化这些代码的最佳方法或者那一点,很少有确凿的证据表明任何人都可以像你似乎那样测量任何东西。

哎呀,我非常喜欢你的问题,我正在推动它。

此致

标记

答案 4 :(得分:3)

首先优化自己的时间。不要打扰尝试盲目地列出和/或应用优化。不要浪费时间将返回值转换为引用参数,因为您不相信编译器会执行NRVO。

不要浪费时间手动将功能标记为inline。不要浪费你的时间来尝试收集某种通用的“优化词典”。

97%的代码只是并不重要性能方面。如果您尝试应用优化,无论它们做什么以及它们有用,您将浪费97%的时间。所有那段时间用于优化实际重要的3%代码。 (顺便说一句,这就是Knuth实际上对“邪恶的根源”引用的意思。不应该执行优化,但除非你有一段特定的代码,你已经知道它是一个热点,你的优化 1)过早,2)无效)

首先是优化建议:关闭此问题,然后请求帮助优化应用中重要的特定代码。您不会通过要求一般优化技巧来了解有关优化应用程序3%的任何有用信息。

第二个优化建议(假设您现在正在查看特定的热点,并且您在选择正确的算法和并行化以及其他高级优化方面已尽力而为): 查看编译器的汇编输出。

第三个优化建议:了解您正在运行的系统。构建代码以利用空间和时间局部性,以最大限度地减少缓存未命中。只需将2D数组的遍历从列主要顺序切换到行主要顺序,就可以轻松地将性能提高一倍。请记住,编译器和CPU都会重新排序指令以提高吞吐量,但分支限制了它们执行此操作的能力,因此请尝试构造代码以获得相当大的基本块,而不会跳入或跳出它们。如果您在支持SIMD指令的CPU上运行,请考虑是否可以有效地使用它们。如果您必须真正深入了解指令级优化,请确保掌握所用指令的延迟。对于浮点重码,请记住,FP指令通常不会被编译器或CPU自动重新排序。由于FP操作具有相当长的延迟,因此依赖链可能是真正的性能杀手。手动打破这些可以显着加快代码速度。同样,避免内存依赖性。首先写入的代码,然后读取地址将变慢。如果无法优化一个或两个内存访问,那么在开始读取之前,您必须等待写入完成(否则可能在后台发生,而不会使CPU停止)。将所有常用数据放在临时数据中,以避免混叠。

至于你所要求的优化“大型复杂数据集”?我完全没有头绪。关于复杂数据集的事情是它们几乎没有共同之处。没有通用方法来优化它们。

最后的建议:听起来你并没有真正编写C ++。你在谈论手动实现动态数组,你在谈论reallocs和MFC类。听起来你正在写“带课程的C”。使用标准库。std::vector已存在。 std::string也是如此。 C ++标准库具有非常高效的良好属性。 MFC课程也不能这样说。

答案 5 :(得分:3)

我很惊讶似乎没有人回应过一点。在您的问题中,您提到使用qsort()。在很多情况下,使用std::sort()代替qsort()可以大大提高速度。改进的大小通常取决于你的比较函数的复杂程度,但我发现2:1的改进相当典型,有时候可能有5:1甚至10:1。

答案 6 :(得分:2)

我同情你的立场,尤其是

  

我一直坐在探查者面前   过去三天的结果

我认为你已经很好地智能地编写了应用程序。现在,这是我的建议:

  • 寻找“通常可以提高性能的任何优秀优化”,只需通过猜测即可。当您知道花时间时,这些将是显而易见的。

  • 执行有更好的方式找出花费时间而不是盯着探查器输出。
    This is how I do it.

我的经验说你有更多的加速空间This is my canonical example.

......祝你好运。让我们知道它是如何运作的。

答案 7 :(得分:1)

+1好问题。

<强>咆哮 我认为你是正确的要求人们不要过早地进行优化 - 这种旧栗子经常被推出作为懒惰设计或草率实施的借口。有一种无偿的欠优化,通常是由于算法选择不当造成的。 结束咆哮

我不知道这97%的东西来自哪里。我被教导了80/20规则 - 20%的代码运行80%的时间,这有趣地似乎也适用于除软件之外的其他东西。总之...

第一个呼叫端口始终是算法 - 确保您使用的是高效算法。一个未经优化的好算法几乎总能击败高度优化的坏算法。我怀疑你已经知道了。

我发现的一些常规优化可能很有用:

  1. 线性搜索通常会导致相当多的开销。通常可以通过二元搜索已排序的集合来替换它们。

  2. 分拣时需要小心。尽管quicksort是一种很棒的通用排序算法,但是当您的集合已经排序或部分排序时,有时候冒泡排序或其他类型的排序会更快。

  3. 插入有序集合 - 可以使用二进制搜索来找到正确的位置而不是天真(虽然通常足够好)实现“坚持到最后,然后排序”。

    < / LI>
  4. 通过更有效地利用缓存,将键与值分开可以帮助您更快地搜索密钥。

  5. 使用双缓冲区并在两个线程作为供应商/消费者进行交互时进行交换,以最大限度地缩短缓冲区需要锁定的时间。线程通常可以更快地处理单独的数据副本,然后再进行修复,而不是让它们跳过其他锁定。

  6. 不要依赖编译器来优化紧密循环中的构造函数和析构函数 - 移动循环外的对象并重用或确认编译器已根据您的意愿对其进行了优化。

答案 8 :(得分:0)

如果您在采样分析器下运行代码并发现有任何计算密集型例程占用了大量时间,那么可以考虑许多微优化,如果您使用SIMD,包括使用SIMD能够限制您对特定CPU的支持。