代码之家  ›  专栏  ›  技术社区  ›  SmacL

C++中良好性能优化的思路

  •  5
  • SmacL  · 技术社区  · 15 年前

    好吧,在过去的三天里,我一直坐在探查器结果前面,通过自动化套件运行相当广泛的测试用例生成的结果。其想法是看看是否有任何好的优化,通常可以提高性能。我有资格 好的 在这种情况下,如下:

    • 有潜力发挥 两者都很好的改进 结尾处有意义且可观察到 用户级别,例如100%改进 在表现不佳的地区。

    • 具有核心空间潜力 减少使用,例如减少50% 在数据密集的区域。

    • 易于实施,且最少 使代码变得模糊,并且最小化 副作用。即 大幅度实施优化 超过成本。

    该应用程序是一个三维绘图和建模软件包,在界面上有大量的图形,在后端进行几何处理。我已经在确保大多数处理的最佳算法选择上做了很多工作,在这个阶段,我正在寻找任何普遍适用的简单方法,以便在处理大型和复杂的数据集时获得额外的支持。到目前为止,我提出了以下几点:

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

    • 排序时,检查数据是否已按排序顺序排列(特别是在使用了qsort的情况下)

    • 将GUI和处理保留在单独的线程中(使 好的 易于实施的标准,但IMO仍然值得)

    • 在大量使用的成员函数中,如果有局部类变量(它们的构造/破坏时间很长),则将它们设置为私有类成员。尤其是动态数组和字符串,尤其是MFC Carrays和CString。

    • 使用动态数组时,将初始大小设置为稍微超过典型使用量,并采用指数增长策略。

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

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

      cstring myfunc(双x,双y)

    效率低于

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

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

    这些项目在我的环境中似乎工作得很好,但是是否有明显的项目是我遗漏的,或者在优化方面是否有其他好的资源?

    编辑: 根据所提供的许多评论,需要进一步的解释。虽然我完全认识到建议对特定代码段进行特定的优化需要看到该代码,但过去几天分析探查器输出所花费的时间已经在优化候选方面显示出了某些模式。我也意识到我自己对其他人在该领域中所做的有益的事情一无所知,并且(至少对我而言)看到拥有一份列举的此类技术清单的价值,不管它们是否适用于我的情况。这个问题不是关于优化的危险,但是对于任何初学者来说,我建议您首先建立一个强大的需求,以便在开始考虑之前进行优化。我自己的偏好是在设计阶段根据未来的性能需求进行大多数优化,但我也强烈主张进行分析,以验证在实现过程中是否满足了设计假设。我会要求人们将他们的答案局限于他们自己的积极优化经验,而不是他们的信仰 我们是否应该首先考虑优化。

    fwiw,让编译器优化代码与不优化代码之间的差异在我的自动化套件中以12%的比例出现,这在最终用户级别是可以观察到的。

    第二次编辑: 我发现一些相关的帖子非常有益,特别是迈克·邓拉维关于过分依赖于探查器输出的评论。

    Performance optimization strategies of last resort?

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

    9 回复  |  直到 15 年前
        1
  •  5
  •   philsquared    15 年前

    CString MyFunc(double x, double y)

    效率低于

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

    如果myfunc写得很干净,它们应该差不多。编译器应该能够利用 NRVO . 听起来你已经分析过了,发现它不是——我只是说它可能更符合你的标准,比如“代码的最小模糊度”,重新排列函数本身,让nrvo发生。

    还有几件事要尝试:

    1. 内存化(类似于缓存 重复搜索,但更多 专注于树/图分辨率, 如果有的话)。
    2. 如果不需要额外的 精确性(如果你 罐头)
    3. 使用类型标记假设(您提到了排序数组- 另一个常见的是低成本的 字符串)创建派生或 包装类型(例如 Sorted<T> ) 明确这些假设。那 如果你有一个方法 一 Sorted<vector<T> > 例如, 你给它一个排序向量,它 直接通过-但是如果你 给它一个 vector<T> ,它必须 构建了一个 排序的矢量 在 哪一点它会排序。你可以 手动断言已排序 使用另一个构造函数, 但是它使它更容易携带 你的假设,也许 抓住你可能有的地方 否则会错过。
    4. 不要放弃入口等。确保你完全知道什么时候 他们应该帮忙,什么时候 应该阻碍。在某些情况下,他们 真的可以改变-但是 如果你只是处理 他们任意。
    5. 你可能会从中受益 flyweight pooled object allocators .
    6. 线程时, 尽量减少相互作用 你可以减少代码的数量 这需要锁定。经常采取 甚至相当昂贵的副本 对象可以减少开销 比互斥。明显的利用 原子类型。
        2
  •  9
  •   dirkgently    15 年前

    请不要扔掉任何旧的 “乐观是万恶之源” 什么,这完全与此无关 问题

    是的,然后你就有了:

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

    我想知道你是否在使用一种有效的算法。这是“过早优化是万恶之源”状态的基本前提。

    将被否决。

    真正地?那口气不好。IMO。

    同样,我对 让编译器优化的乐趣 是给我的,还是把所有的东西都扔进去 在这个地方。fwiw,区别 让编译器优化 代码是否以12%输出 在我的自动化套件中, 终端用户可观测的边界线 水平。

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

    除此之外,由于您没有提供关于瓶颈所在位置的特定见解,因此很难(如果不可能)提供任何指针。我敢猜测你至少:

    • 使用自定义分配器进行游戏
    • 如果目标机器是向量机,探索使用向量指令的可能性

    编辑:既然您说您不知道rvo:请尝试阅读移动语义,尤其是这个库: move library 来自土坯。我想Boost也会有类似的东西。

    编辑2:还有两件事:

    • 定点算术可能是一个好的查找方法
    • 尽可能多地使用查找表
        3
  •  3
  •   Charles Eli Cheese    15 年前

    我认为你的需求几乎是互斥的,除非有某种明显的缺陷(所有的分析都是很好的发现)。

    真正改变性能的事情需要付出很多努力,而您的基本数据结构是最重要的。减少内存碎片、对齐内存管理、SIMD、尽可能小并尽可能多地分配在一个块中的数据结构、多线程、减少模板中的代码大小、将参数重新定义为局部变量,以便优化器知道它们是相同的,以进行优化。没有一个可以在没有大量成本的情况下在结尾处附加。

    很多时候,你甚至不能很容易地测量那些真正影响性能的因素,因为它们只会随着程序的运行或代码大小的增长而变得昂贵。

        4
  •  3
  •   High Performance Mark    15 年前

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

    当然,我会把这一切和地狱相提并论。除非你有多台电脑,否则不可能。哦,备忘录就在里面,我们这几天都有!!

    祝你好运,一定要让我们知道你是怎么相处的。我读了很多关于优化这段代码或那段代码的最好方法的废话,很少有确凿的证据表明任何人都会像你所做的那样去衡量任何事情。

    我非常喜欢你的问题,我反对。

    当做

    作记号

        5
  •  3
  •   Stack Overflow is garbage    15 年前

    从优化自己的时间开始。不要 打扰 尝试盲目地列出和/或应用优化。不要因为不信任编译器而浪费时间将返回值转换为引用参数。

    不要浪费时间手动将函数标记为 inline . 不要浪费时间去收集一些通用的“优化字典”。

    97%的代码 没关系 性能方面。如果您尝试应用优化,不管它们做什么和在哪里有用,您将浪费97%的时间。一直以来 能够 已经花了3%的时间优化实际重要的代码。(顺便说一句,这就是Knuth用“万恶之源”这句话的真正含义。不是说不应该执行优化,而是除非您有一段已经知道是热点的特定代码,否则您的优化 1)过早,2)无效)

    所以,第一个优化建议是:关闭这个问题,然后寻求帮助来优化应用程序中重要的特定代码。你不会学到任何有用的东西来优化你的应用程序的3%,重要的是通过要求 一般的 优化技巧。

    第二个优化建议(假设您现在正在查看一个特定的热点,并且在选择正确的算法、并行化和其他高级优化方面已经尽了最大努力): 查看编译器的程序集输出。

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

    如你所要求的那样优化“大型复杂数据集”?我完全不知道。关于复杂数据集的问题是它们几乎没有共同点。没有 一般的 优化它们的方法。

    最后一个建议:听起来你并不是真的在写C++。你说的是手动实现动态数组,你说的是reallocs和mfc类。听上去你在写“C和班级”。使用标准库。 std::vector 已经存在。所以 std::string . C++标准库具有很好的属性,它非常高效。对于MFC类也不能这样说。

        6
  •  3
  •   Jerry Coffin    15 年前

    我很惊讶似乎没有人对这一点做出回应。在你的问题中,你提到使用 qsort() .在很多情况下,使用 std::sort() 而不是 qSO() . 改进的大小通常取决于您的比较函数有多复杂,但我发现2:1改进相当典型,有时5:1甚至10:1是可能的。

        7
  •  2
  •   Community PPrice    7 年前

    我很同情你的立场,尤其是

    我一直坐在轮廓仪前 过去三天的结果

    我想你已经做好了智能编码应用程序的工作。现在,我建议:

    • 寻找“任何能普遍提高性能的好的优化”,你只需通过猜测就可以了。当你 知道要花多少时间 .

    • 有更好的方法 找出所花时间 而不是盯着分析器输出。
      This is how I do it.

    我的经验是 你还有很多加速的空间 . This is my canonical example.

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

        8
  •  1
  •   markh44    15 年前

    +1个好问题。

    咆哮 我认为你要求人们不要抱怨过早的优化是正确的——通常这个老栗子被推出来作为懒惰设计或草率实现的借口。有这样一种情况,即在优化下是没有意义的,通常是由于算法选择不当造成的。 结尾咆哮

    我不知道这97%的东西是从哪里来的。我被教导80/20规则——20%的代码运行80%的时间,有趣的是,这似乎也适用于除软件以外的其他事情。总之…

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

    我发现一些一般的优化是有用的:

    1. 线性搜索通常会导致相当多的开销。通常可以用二进制搜索已排序的集合来替换这些内容。

    2. 分类时需要小心。虽然QuickSort是一种很好的通用排序算法,但有时当您的集合已经排序或部分排序时,气泡排序或其他类型的排序速度更快。

    3. 插入到一个已排序的集合中——可以使用二进制搜索来找到正确的位置,而不是简单的(尽管通常足够好)实现“坚持到底,然后排序”。

    4. 从值中分离出键应该有助于通过更有效地利用缓存来更快地搜索键。

    5. 当两个线程作为供应商/消费者进行交互时,使用双缓冲区和交换,以最小化需要锁定缓冲区的时间。线程在单独的数据副本上工作并稍后修复通常要快一些,而不是让它们彼此锁定。

    6. 不要依赖编译器来优化紧密循环中的构造函数和析构函数——要么将对象移出循环并重新使用,要么确认编译器已经按照您希望的方式对其进行了优化。

        9
  •  0
  •   Paul R    15 年前

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