【问题标题】:General C++ Performance Improvement Tips [closed]一般 C++ 性能改进技巧 [关闭]
【发布时间】:2010-01-08 19:37:12
【问题描述】:

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

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

【问题讨论】:

  • 多年来我养成了一些优化的编程习惯(来自汇编语言和 8 位微控制器)。问题是,很多时候,一个简单的设计更改就会使 {micro} 优化无效。我最好的优化是减少不必要的方法和对象的编码。如有必要,总会有时间添加更多功能。
  • 对于大多数语言,尤其是像 C++ 这样复杂的语言,如果你没有做一些非常不称职的事情(比如每次通过循环复制大量数据),几乎不可能确定有什么影响“设计时”优化有——包括这些小习惯。我的建议是 [Steve McConnell's] (stevemcconnell.com):编码时根本不值得担心优化。养成编写可读代码而不是优化代码的习惯。
  • 但是如果您正在编写实时应用程序,线性时间也很重要,并且在应用程序的开发过程中,瓶颈可能会转移到另一个地方 - 如果您之前仔细编码,事情可能会运行得更快
  • 这里真正的问题是什么?我能从文本中解读出的最好的内容是极其广泛且自相矛盾的(关于性能的一般建议,而不是算法复杂性,然后是一些只对性能影响“一点点”的一般编程习惯?)。
  • 完全正确 :) 我只是在寻找肯定不会伤害的习惯,但可以让代码变得更好

标签: c++


【解决方案1】:

Effective C++More Effective C++Effective STLC++ Coding Standards 中的许多提示都在这条线上。

这种技巧的一个简单示例:尽可能使用前增量 (++i) 而不是后增量 (i++)。这对于迭代器尤其重要,因为后增量涉及复制迭代器。您的优化器也许可以撤消此操作,但编写 preincrement 并不是任何额外的工作,所以为什么要冒险呢?

【讨论】:

  • 我听说一些人将编写效率较低且读/写不那么容易的代码的做法称为“过早悲观化”。
  • 我使用它不是因为它更快,而是因为我发现它更具表现力。 “增量 i”写成“++i”。后增量与前增量一样可能导致逻辑错误 - 问题是如果您使用错误的,您会得到错误的答案。
  • 我个人讨厌看到 ++i。对我来说,我喜欢在操作员左侧看到正在修改的对象。在现代编译器上,无论如何它都不会产生任何影响。
  • 编译器可以优化i++,使其在基元方面与++i一样高效,但对于迭代器则不行,并且一致性很好。
  • @BillyONeal,尝试读取像++i 这样的预增量:“increment i”,然后将其与i++:“i increment”进行比较。哪个听起来更合适?希望现在您不再讨厌这种增量形式。 :)
【解决方案2】:

如果我的理解正确,您是在询问避免过早悲观,这是避免过早优化的一个很好的补充。根据我的经验,要避免的第一件事是尽可能不要复制大对象。这包括:

  • 通过 (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

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

【讨论】:

  • 你听说过复制省略吗?
  • 那么在不创建副本的情况下正确的做法是什么? (在你的例子中)
  • @Inverse:我听说过,但我不希望它发生在编译单元之间。
  • @Mike:RVO 不需要内联(毕竟,它在语义上等同于从调用者传递一个指向目标位置的指针,并在被调用者中对其进行新的放置),因此是不受翻译单元边界的影响。
  • 我会阅读 (cpp-next.com/archive/2009/08/want-speed-pass-by-value) 并思考为什么以及何时您可能想要使用复制语义而不是盲目地在任何地方开 const&
【解决方案3】:

我的一些小烦恼:

  1. 不要在使用/初始化对象变量之前声明(实际上是定义)对象变量(如在 C 中)。这需要构造函数和赋值运算符函数将运行,并且对于复杂的对象,可能会很昂贵。
  2. 更喜欢前增量而不是后增量。这仅对迭代器和具有重载运算符的用户定义类型很重要。
  3. 尽可能使用最小的原始类型。不要使用 long int 来存储 0..5 范围内的值。这将减少整体内存使用量,提高局部性,从而提高整体性能。
  4. 仅在必要时使用堆内存(动态分配)。许多 C++ 程序员默认使用堆。动态分配和解除分配的成本很高。
  5. 尽量减少临时变量的使用(尤其是基于字符串的处理)。 Stroustrup 提出了一种很好的技术,用于在“C++ 编程语言”中定义三元和高阶算术运算符的逻辑等价物。
  6. 了解您的编译器/链接器选项。还知道哪些会导致非标准行为。这些会极大地影响运行时性能。
  7. 了解 STL 容器的性能/功能权衡(例如,不要频繁插入向量,使用列表)。
  8. 当变量、对象、容器等要被无条件赋值时,不要初始化它们。
  9. 考虑复合条件的计算顺序。例如,给定if (a && b),如果 b 更有可能为假,则将其放在首位以保存 a 的评估。

还有许多其他“坏习惯”我不会提及,因为在实践中,现代编译器/优化器会消除不良影响(例如,返回值优化与传递引用、循环展开等) .).

【讨论】:

  • 出于软件维护的原因,我不同意#8。仅仅因为某些东西最初被无条件地分配给并不意味着它总是会如此。对于绝大多数情况,初始化变量是一个非常好的主意。
  • 我不同意#3。如果处理器的本机字是 32 位,则将其优化为处理 32 位;无论您使用全部还是更少。一些处理器在尝试访问较小大小的变量时可能会阻塞或阻塞。比如ARM处理器8 | 32 位模式(8 位字符,32 位整数)很难处理 16 位数量。因此,与 int(32 位)相比,短的 int(16 位)实际上可能会减慢处理器的速度。必要时使用整数和短整数。
  • 我无法与 ARM 处理器交谈,但在 x86s 上,如果您的变量跨越内存中的字边界,您只会遇到这样的问题。当您使用不是字长的变量时,更有可能发生这种情况。当然,大多数编译器会将内存中的变量与字长对齐,从而避免这个问题(并且可能会使用与最初使用字长变量相同的内存量)。跨度>
  • +1 主要是好的建议,尽管我也不同意#3。尽管您需要至少回到 10 年才能找到不能处理 16 位数量的 ARM。
  • #3 是错误的。几乎在所有情况下,比整数更小的单位都会损害性能。否则,很好的清单。
【解决方案4】:

Agner Fog 的“Optimizing Software in C++”通常是优化技术的最佳参考之一,既简单又更高级。另一个很大的优势是可以在他的网站上免费阅读。 (他的网站见他名字的链接,pdf的论文标题链接)。

编辑:还要记住,90%(或更多)的时间花在 10%(或更少)的代码上。所以总的来说,优化代码实际上是关于查明你的瓶颈。此外,重要且有用的是,现代编译器会比大多数编码器更好地进行优化,尤其是诸如延迟变量初始化等微优化。编译器通常非常擅长优化,因此请花时间编写稳定、可靠的代码和简单的代码。

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

【讨论】:

  • 根据我的经验,应该在程序的正确性和健壮性上花费更多的精力而不是性能。与很少锁定或崩溃的较慢程序相比,经常锁定或崩溃的高性能程序几乎没有价值。
  • 您应该追求正确性和高性能。人们总是让两者相互对抗,就好像你不能同时拥有两者一样,但你可以而且应该。
  • @Dan 我完全同意你的看法。但是我见过很多代码,人们真诚地尝试优化代码,唯一的结果就是让编译器更难优化代码。我认为您可以真正提高程序性能的一个地方是仔细选择您的设计和算法。当然,需要一个分析器来识别那些占用 90% 的 10% 代码,并确定它是否可以进一步优化(以及如何优化)。
  • 你的程序应该是完全正确的(如果它完全正确,它要么是昂贵的要么是微不足道的)。它应该在您的目标受众认为的低端系统方面具有足够的性能。你应该有智慧不要为编译器搞砸事情。
  • 我喜欢 Agner Fog 的作品,但其中很多内容都依赖于处理器。如果您的程序必须在多种处理器上运行,这并不总是有帮助。
【解决方案5】:

一个很好的起点是Sutter's Guru of the week 系列,以及由此产生的Exceptional C++书籍。

【讨论】:

    【解决方案6】:

    使用函子(实现了operator() 的类)而不是函数指针。编译器内联前者更容易。这就是为什么 C++ 的 std::sort 往往比 C 的 qsort 性能更好(当给定函子时)。

    【讨论】:

    • std::sort 在很大程度上也做得更好,因为它还可以避免额外间接的需要(传递两个对象的地址被比较为void*,而不是对象本身,即使是一个普通的副本更便宜)。
    • @PavelMinaev 这就是弗雷德所说的。比较器可以内联,避免函数调用。减少间接性。您是否分析过传递一小块内存,可能是int,而不是传递对它的引用?我预测两者的速度相等。我对它进行了基准测试,结果是一样的。
    【解决方案7】:

    从您的问题看来,您已经知道“过早的优化是邪恶的”哲学,所以我不会宣扬这一点。 :)

    现代编译器已经非常聪明地为您进行微优化。如果你太努力,通常会使事情变得比原始的直接代码慢。

    对于小的“优化”,您可以不假思索地安全地进行,并且不会对代码的可读性/可维护性产生太大影响,请查看C++ 编码标准一书的“过早悲观化”部分 由 Sutter 和 Alexandrescu 撰写。

    有关更多优化技术,请查看 Bulka 和 Mayhew 的 Efficient C++。仅在通过分析证明合理时使用!

    有关良好的通用 C++ 编程实践,请查看:

    • C++ 编码标准,作者 Sutter 和 Alexandrescu(必须拥有,恕我直言)
    • Effective C++/STL 系列,作者 Scott Meyers
    • 卓越的 C++ 由 Herb Sutter 编写的系列

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

    // 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 的一部分,最好不要内联它,不管它有多小。这是因为内联函数的实现必须对其他模块/类可见。如果您更改了该内联函数/方法中的某些内容,则需要重新编译引用它的其他模块。

    当我第一次开始编程时,我会尝试对所有内容进行微优化(那是我的电气工程师)。真是浪费时间!

    如果您喜欢嵌入式系统,那么事情就会发生变化,您不能将内存视为理所当然。但这又是一大堆蠕虫。

    【讨论】:

    • 哇,我写的时间太长了。已经被大家覆盖了!
    • 这被称为“西方最快的枪”(见meta.stackexchange.com/questions/9731/…)。以下是如何击败它(Jon Skeet 使用这种方法)。尽快输入您的基本简短答案并提交。这会将您的答案放在那里,您将立即有资格获得投票。然后,返回并编辑您的帖子,直到您完全满意为止。
    • @Emile_Cormier(非 SO 讨论:cantgrokwontgrok.blogspot.com/2008/09/… 滚动至“Bob Munden 效应”)向他们展示男性女性也可以编程。 8-]
    • @rib:咳咳……埃米尔是一个法语男性名。最后一个“e”是静音的。不要与艾米莉混淆。 :-)
    • 您是否分析过通过引用传递比通过值传递更小的对象慢?我预测在编译器优化后它不会变慢,并且对于只读访问可能会稍微快一些,因为数据不需要复制并且可能会改变程序集以直接从分配的小对象中读取。
    【解决方案8】:

    这是一篇关于该主题的好文章:How To Go Slow

    【讨论】:

      【解决方案9】:

      使用正确的容器

      序列容器

      • 如果要继续向其中添加数据,请不要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

      【讨论】:

      • 我同意除了第一点之外的所有内容。 vector 呈指数增长,因此在多次调用push_back 之前不调用reserve 的成本是微不足道的。 (如果您知道新尺寸,最好致电reserve,但不要因为您不知道而避免使用vector)。此外,deque 在这种情况下也不会更好。
      • 无序关联容器不一定更快。如果您必须重新散列很多内容(您的分析器可以告诉您),它们可能会明显变慢。
      • @Mike Seymour:大多数实现都是如此,但不一定如此。虽然如果不重新分配vector 插入可能比deque 插入更快,但重新分配大型vector 的成本是巨大的。更糟糕的是,它是一次性集中的,因此非常大的vector 可能会根据应用程序产生明显的延迟。
      • 我想补充第一点;如果您的访问频率比向容器中添加数据的频率高,那么 std::vector 可能是正确的选择,即使 deque、set、map 或 list 看起来更方便。
      • 关于std::vectorstd::deque 的建议并不完整。显然,如果您知道将添加多少项目,则有关 std::vector::reserve 的部分很好。 std::vector 在线性扫描或任何读取或更改彼此附近数据的访问模式时速度更快。完整的图片包括所有使用 - 不仅仅是使用push_back 很少或很多。 std::deque 遭受间接以及并非所有数据都是连续的数据的影响,这通常意味着更多的缓存未命中。
      【解决方案10】:

      除非您确实确定另一种容器类型更好,否则请使用“std::vector”。即使 ´std::deque、´std::list´、´std::map´ 等似乎是更方便的选择,但向量在内存使用和元素访问\迭代时间方面都优于它们。

      另外,更喜欢使用容器成员算法(即 'map.equal_range(...)')而不是其全局对应算法('std::equal_range(begin(), end()...)')

      【讨论】:

      • 内存是最便宜的资源。除非您正在编写具有严格内存需求的代码,否则每个元素带有一些额外指针的容器不会消耗太多内存。 std::vector 在不知道用例的情况下不能推荐给其他容器。是的,std::vector 在线性扫描或经常访问附近元素时最快。不,这并不意味着永远不要使用std::dequestd::liststd::mapstd_unordered_map 等。一个简单的例子是需要经常在整个容器中添加或删除元素,同时保持顺序和重复,@987654327 @ 通常会很好用。
      【解决方案11】:

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

      一个例子总是使用智能指针而不是原始指针来控制堆内存的生命周期。当然,另一个相关的是养成始终使用 RAII 进行资源获取和释放的习惯。另一个总是使用异常来处理错误。 这三者倾向于简化代码,从而使其更小、更快、更容易理解。

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

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

      还有一件事——如果你需要这个功能,不要将代码从一个地方复制到另一个地方——使用一个函数。这样可以保持较小的代码大小,并且可以更轻松地优化所有使用此功能的地方。

      【讨论】:

      • 一个很好的答案,但可以使用一些分节符:)
      • 我必须努力培养这种习惯。
      • 出于某种原因我回来看这个。这么多段!让我想起了我在某处读到的那句话,人类最强烈的冲动是纠正别人草稿的冲动。
      • 即使是 Bjarne Stroustrup,那个在语言中添加异常的人,也承认它们不如错误检查等替代方法性能好。如果速度是唯一目标,建议完全避免异常并使用noexcept 提示代码展开堆栈并处理不需要生成的异常。此外,它可以提高其他情况下的性能,例如std::vector 在移动对象时如何使用move_if_noexcept。移动将比副本更快。它会对性能产生巨大影响。
      • 你认为智能指针会比原始指针快吗?正确的设计是智能指针,但它永远不会比原始指针快,因为最好的情况是优化器生成使用原始指针的代码。我同样不确定 RAII 会比在析构函数之外运行相同的代码更快。同样,理想情况下,这两种方法将彼此一样快,并且使用 RAII 是更好的设计。但是,RAII 并不是速度优化。
      【解决方案12】:

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

      考虑以下几点:

      #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 组成。

      模板还可以用于各种不同的事物,例如特征、类专业化,甚至用于将 int 值传递给类!

      【讨论】:

      • 模板也可能导致代码膨胀,因为它们是代码模板。编译器将为每种数据类型生成类似的代码。
      • 和代码膨胀会导致缓存未命中,这是它干扰性能的主要方式。
      • “模板也可​​能导致代码膨胀......” - 对于非模板代码也可以这样说。在这两种情况下,这都取决于您如何使用/实现相关代码。
      • @Thomas Matthews:使用模板肯定会花费更多时间进行编译,但执行速度会更快。
      • @ThomasMatthews 如果您需要该代码,您要么必须为您想要的每种类型编写它,要么编写一个模板。不应该有代码膨胀。但是,该过程可能会增加编译时间,因为必须完成更多工作。
      【解决方案13】:

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

      【讨论】:

        【解决方案14】:

        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++ 编码标准也很重要,因为它解释了很多关于语言和 STL 中发生的事情,允许您可以更好地了解具体情况,从而更好地优化您的具体案例。

        【讨论】:

        • 目前最全面的链接
        【解决方案15】:

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

        【讨论】:

          【解决方案16】:

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

          【讨论】:

            【解决方案17】:

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

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

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

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

            最后,我想提一下 Tony Albrecht 的 Pitfalls of Object Oriented Programming 演示文稿:并不是说它包含您可以盲目遵循的经验法则,而是非常有趣的阅读。

            【讨论】:

            • 在一个函数中有很多返回值可能会有问题...
            • 也许我选择了一个糟糕的例子,但重点是声明变量并仅在使用时初始化它们,而不是上面的 200 行——现在你可以争辩说 200 行长的函数可以有问题;)
            • 我会说在大多数情况下这并不重要。通常,这些小优化中的许多在开发过程中的破坏性大于它们的用处。现代编译器可以很容易地找出如何延迟变量的初始化,直到它们被使用。最重要的是保持代码简洁(可读)。
            • “现代编译器可以很容易地找出如何延迟变量的初始化,直到它们被使用。” - 当后者导致可观察到的副作用时,编译器无法重新安排初始化。可观察到的副作用的一个特殊情况是使用new 分配内存(考虑重载运算符等),因此如果某些类的构造函数在任何时候调用new,它通常不会被延迟初始化。
            • 变量的初始化和声明应该在使用它们附近以提高可读性。在某些情况下它可能会更快,例如早期的return,但它通常与通常在return 之前运行的函数的整个逻辑相同。不过,这不是一个不正确的微优化。 @TommyAndersen 您指的是哪种优化?像通过常量引用传递这样的大问题通过编码意图增加了可读性。我也不知道编译器会重新排序初始化。你有那个来源吗?
            【解决方案18】:

            这里已经有很多好的建议了。

            养成好习惯的最好方法之一就是强迫自己养成好习惯。为此,我喜欢 PC-Lint。 PC-Lint 实际上会强制执行 Scott Meyer 的 Effective C++ & More Effective C++ 规则。此外,遵守 Lint 规则往往会导致更容易维护、更少出错和更清晰的代码。当您意识到 lint 通常会产生比源代码更多的输出时,请不要太疯狂;我曾经做过一个项目,有 150MB 的源代码和 1.8GB 的​​ Lint 消息。

            【讨论】:

              【解决方案19】:
              1. 避免内存碎片。
              2. 对齐内存。
              3. SIMD 指令。
              4. 无锁多线程。
              5. 使用合适的加速树,如kd-tree、cover tree、octree、quadtree等。 5a。以允许前三个的方式定义这些(即,将节点全部放在一个块中)
              6. 内联。最低悬但相当美味的水果。

              您可以通过这种方式获得惊人的性能提升。对我来说 1500 次用于计算繁重的应用程序。不是蛮横的,而是在主要软件包中编写的类似数据结构。

              我不会像帖子上的预增量那样卡住。这只会在某些(不重要)的情况下节省开支,而且提到的大部分内容都是类似的东西,可能偶尔会在这里和那里额外节省 1%,但通常不值得费心。

              【讨论】:

              • 请记住,并发需要启动线程并且需要划分工作,这两者都需要时间。顺序算法将在相当长的一段时间内胜过并行算法。只有当要做的工作量足够大(要做的工作量及其复杂性的某种组合)时,它才变得值得。例如,我发现 Collection::stream 对于 Java 中的许多数据集比 Collection::parallelStream 更快,因为最初的工作是并行化操作。
              【解决方案20】:

              这个会有很好的通用c++优化技巧:

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

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

              我将 valgrind 与 callgrind 工具一起用于分析目的,它会告诉您哪些线路的成本是多少。

              valgrind --tool=callgrind

              【讨论】:

                【解决方案21】:

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

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

                【讨论】:

                • 为什么不使用宏?我认为那些是编译的东西,因此应该是最快的,不是吗?虚拟析构函数背后的想法是什么?即使不需要,这也会自动添加一个 vft 指针,不是吗?
                • 有充分的理由避免使用宏并在需要时实现虚拟析构函数,但它们与性能无关。
                • 1- A "#define 由预处理器扩展,而常量由编译器处理并包含在用于调试代码的符号信息中。2- 如果您的类将被子类化,请声明一个析构函数virtual 以正确销毁派生对象,并考虑基指针。
                • 2 和 3 看起来不错,但我看不出它们与性能有何关系。
                • @MikeSeymour 一个虚函数在调用该函数时添加了一个 vft 指针和一个间接步骤。有些人甚至使用某些设计来避免虚拟功能,例如奇怪地重复出现的模板模式。如果经常调用有问题的函数,它会极大地影响性能。你说对了一半,因为添加虚拟不会提高速度。
                【解决方案22】:
                • 避免多重继承。
                • 在必要时使用虚拟,而不仅仅是为了好玩
                • 只有在不方便时才使用模板化集合类

                【讨论】:

                • 不一定要避免多重继承。这是来自 Java、C# 和可能其他一些语言的信念,这些语言具有限制程序员为安全而做的事情的哲学。如果你知道你在 C++ 中做什么,那就没有错。
                • 为什么要避免多重继承?模板化集合有什么问题(除了与简单数组相比的数据开销)
                • 多重继承对性能的影响很小,因为 this 指针有时需要调整以指向不同的基类,并且有时通过包装器调用成员函数。但这肯定不足以保证影响您设计类层次结构的方式。
                • @Partial 像 Java 这样的语言禁止多重继承以减少常见的编程错误。当它们不包括运算符重载并且当它们使所有内容都成为按值传递的对象引用时(除了原语,尽管它们也按值传递)时,存在类似的设计目标。我可以写几页关于 Java over C++ 的有意简化。哲学与安全无关。为了抵消引入的限制,他们添加了接口来“继承”多种行为。
                【解决方案23】:

                为什么到目前为止没有人提到它?为什么大家都变成穷小子++i

                您可以轻松做到的最好的小事之一,不要悲观您的代码:

                Effective C++ Scott Meyers,第 20 项:

                首选传递引用到常量而不是传递值

                例子:

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

                如果是短的std::string,你可能赢不了多少,但是传递像这样的 对象,可以为你节省相当多的计算能力,因为你避免了冗余复制。如果您忘记实现复制构造函数,还可以避免一两个错误。

                【讨论】:

                • 哦,笨蛋,克里斯托已经推荐过了。
                猜你喜欢
                • 2010-12-14
                • 1970-01-01
                • 1970-01-01
                • 2010-09-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-07-12
                相关资源
                最近更新 更多