【问题标题】:Should we still be optimizing "in the small"?我们还应该“从小”优化吗?
【发布时间】:2010-10-20 07:17:33
【问题描述】:

我正在将我的 for 循环更改为使用 ++i 而不是 i++ 并开始思考,这真的有必要了吗?当然,今天的编译器自己进行这种优化。

在这篇文章中,http://leto.net/docs/C-optimization.php,Michael Lee 从 1997 年开始讨论其他优化,例如内联、循环展开、循环干扰、循环反转、强度降低等。这些还有意义吗?

我们应该进行哪些低级代码优化,哪些优化可以安全忽略?

编辑:这与过早优化无关。 优化的决定已经做出。现在的问题是什么是最有效的方法。

轶事:我曾经看过一个需求规范,上面写着:“程序员应该左移一位而不是乘以 2”。

【问题讨论】:

  • 您的问题似乎是一个比我最初想象的更普遍的关于编译器当前最新技术的问题。我发现最有用的答案就是查看我选择的编译器发出的程序集。但只有在我尝试了其他所有方法之后。
  • 请注意您的编辑:如果您不知道要优化什么,那么所有优化都为时过早。仅仅知道“我们需要优化”是不够的。在您知道“我们需要优化 这段 代码”之前,任何优化尝试都为时过早。

标签: c++ c optimization


【解决方案1】:

只有当您确定它们是相关的时。这意味着您之前已经在特定编译器上调查过此问题,或者您已经完成了以下操作:

  1. 产生的功能代码
  2. 分析该代码
  3. 已识别的瓶颈
  4. 简化设计以消除瓶颈
  5. 选择的算法可以最大限度地减少瓶颈调用

如果您已经完成了所有这些事情,那么最好的办法通常是让您的编译器发出一些较低级别的内容,您可以检查自己(如汇编)并据此做出具体判断。根据我的经验,每个编译器都有点不同。有时对一个优化会导致另一个产生更大的效率较低的代码。

如果您还没有做过这些事情,那么我将其称为过早优化,我建议您不要这样做。在做这些事情之前进行优化所带来的回报与所涉及的成本相比是不成比例的。

【讨论】:

  • 当涉及到用户定义的增量运算符(例如迭代器运算符)时,实际上并不是过早的优化,其中 i++ 意味着复制原始对象,这可能代价高昂。
  • 这与过早优化无关。优化的决定已经做出。现在的问题是什么是最有效的方法。
  • @cpitis 也可能不是,尤其是当您考虑使用来自通过慢速网络访问的数据库中的数据填充两倍数量的相同对象时。似乎最好编写代码并让执行时间来决定它是否是一个问题。
  • @Mark 我发现最有用的答案就是查看编译器发出的程序集。但只有在我尝试了其他所有方法之后。你的问题似乎是一个比我最初想象的更普遍的关于编译器当前最先进技术的问题。
  • 在设计时在两个其他等效选项之间做出选择,其中一个已知更快,这并不是过早的优化。首选 ++i 没有任何成本,因此请使用它。不要等到它变成性能问题,因为到那时优化不再是免费的(你必须返回并更改现有代码,当你可以在相同的时间内正确编写它开始时)
【解决方案2】:

编译器能够更好地判断和做出此类决定。您所做的微优化可能会发挥作用,最终会错过重点。

【讨论】:

  • 我同意,这就是为什么预优化通常是个坏主意。
【解决方案3】:

我想补充一点。这种“过早的优化是不好的”是一种垃圾。 当你选择一个算法时你会怎么做?您可能会选择时间复杂度最高的那个 - OMG 过早优化。然而,每个人似乎都对此感到满意。所以看起来真正的态度是“过早的优化是不好的——除非你按照我的方式去做” 在一天结束时,做任何你需要做的事情来制作你需要制作的应用程序。

“程序员应该左移一位而不是乘以 2”。 希望你不想再乘以浮点数或负数;)

【讨论】:

  • 当我选择算法或数据结构时,我的第一选择是最容易编码或使用的那个——直到以后我才考虑性能。
  • 你还需要记住,大O对小N无关紧要。但我同意你的观点,这句话需要澄清。快速设计总是首选 - 这个短语可以用来表示直到很晚才考虑性能,这意味着您甚至没有处理性能问题的计划。
  • 如果在大多数情况下选择算法,我不会称之为优化。我称之为设计。这是关于时间投入和实际速度改进之间的权衡。这也是关于模棱两可的。在您尝试它们并且搜索空间非常大之前,您并不确切知道每个语义等效的句法构造如何执行。另一方面,很容易发现常见算法的复杂性。
  • 好吧,也许我只是误解了人们对“优化”的看法,我非常同意你的观点,即“总是首选快速设计”
【解决方案4】:

这些优化仍然适用。关于您的示例,在内置算术类型上使用 ++i 或 i++ 无效。

如果是用户定义的递增/递减运算符,++i 更可取,因为它并不意味着复制递增的对象。

所以一个好的编码风格是在 for 循环中使用前缀递增/递减。

【讨论】:

  • ++i 和 i++ 如果 "i" 是一个 stl 迭代器,则可以产生很大的不同。一个必须返回(副本)原始迭代器,另一个不需要。
【解决方案5】:

一般来说,不会。编译器更擅长在整个代码库中进行像这样的小型、直接的微优化。通过使用正确的优化标志编译发布版本,确保您在此处启用编译器。如果您使用 Visual Studio,您可能希望尝试优先考虑大小而不是速度(在很多情况下,小代码更快)、链接时代码生成(LTCG,它使编译器能够进行交叉编译优化),甚至可能是配置文件引导的优化。

您还需要记住,从性能角度来看,您的大部分代码都无关紧要 - 优化此代码不会产生用户可见的效果。

您需要尽早定义绩效目标并经常进行衡量,以确保您能够实现这些目标。如果超出您的目标,请使用分析器等工具来确定代码中的热点位置并进行优化。

正如另一位发帖者here 所提到的,“没有测量和理解的优化根本不是优化——它只是随机变化。”

如果您已测量并确定特定函数或循环是热点,则有两种方法可以对其进行优化:

  • 首先,通过减少对昂贵代码的调用,在更高级别对其进行优化。这通常会带来最大的好处。算法级别的改进属于这个级别 - 算法将更好的 big-O 应该导致运行热点代码更少。
  • 如果不能减少调用,那么您应该考虑进行微优化。查看编译器发出的实际机器代码,并确定它在做什么是最昂贵的——如果结果是复制临时对象,那么考虑前缀 ++ 而不是后缀。如果它在循环开始时进行了不必要的比较,则将循环翻转为 do/while,依此类推。如果不了解为什么代码很慢,任何全面的微优化几乎都是无用的。

【讨论】:

    【解决方案6】:

    先做对,然后再做快 - 基于对性能的衡量。

    选择好算法并尽可能以最易读的方式实现它们。仅当您必须在可读性与性能之间进行权衡时 - 即您的用户说性能是不可接受的,无论是口头还是他们的行为。

    正如 Donald Knuth/Tony Hoare 所说的“过早的优化是万恶之源”——30 年后的今天仍然如此......

    【讨论】:

    • 如果他们的行为是为了竞争对手的产品而放弃你,那么“通过他们的行为”可能为时已晚,就像几年前 SGI 发生的那样。
    • 当然——但“通过他们的主动行动”——向用户发布软件并检查他们的行动并采取行动。绩效衡量始终以人为本,绝不能以技术为导向——用户感知的响应时间才是最重要的。
    【解决方案7】:

    上次我在 Microsoft C++ 编译器上为 STL 迭代器测试 ++it 和 it++ 时,++it 发出的代码更少,因此如果您处于大规模循环中,使用 ++it 可能会获得少量性能提升。

    对于整数等,编译器将发出相同的代码。

    【讨论】:

      【解决方案8】:

      你列出的所有优化现在对 C 程序员来说几乎是无关紧要的——编译器在执行内联、循环展开、循环干扰、循环反转和强度等方面要好得多,减少。

      关于++ii++:对于整数,它们会生成相同的机器代码,因此您使用哪一个取决于样式/偏好。在 C++ 中,对象可以重载这些前置和后置运算符,在这种情况下,通常最好使用前置增量,因为后置增量需要额外的对象副本。

      至于使用移位而不是乘以 2 的幂,编译器已经为你做了。根据架构,它可以做更聪明的事情,例如在 x86 上将乘以 5 变成单个 lea 指令。但是,对于除以 2 的幂的除法和模数,您可能需要多加注意才能获得最佳代码。假设你写:

      x = y / 2;
      

      如果xy 是有符号整数,则编译器无法将其转换为右移,因为它会产生负数的错误结果。因此,它发出右移和一些位旋转指令,以确保结果对于正数和负数都是正确的。如果您知道xy 始终为正数,那么您应该帮助编译器并将它们设为无符号整数。然后,编译器可以将其优化为一条右移指令。

      模数运算符% 的工作方式类似——如果您使用 2 的幂进行修改,则编译器必须发出一个 and 指令,再加上一点点旋转以使结果正确为正和负数,但如果处理无符号数,它可以发出一条and 指令。

      【讨论】:

        【解决方案9】:

        正如其他人所说,如果 i 是某个对象的实例,++i 可能比 i++ 更有效。这种差异对您来说可能很重要,也可能不重要。

        但是,在您关于编译器是否可以为您进行这些优化的问题的上下文中,在您选择的示例中它不能。原因是 ++i 和 i++ 具有不同的含义——这就是为什么它们被实现为不同的函数。 i++ 必须做额外的工作(在递增之前复制当前状态,执行递增,然后返回该状态)。如果您不需要额外的工作,那么为什么要选择其他更直接的形式呢? 答案可能是可读性——但在这种情况下,编写 ++i 已经成为 C++ 中的惯用语,所以我不相信可读性涉及到它。

        因此,如果要选择编写代码来完成不必要的额外工作(这可能很重要,也可能不重要),而这本身没有任何好处,我总是会选择更直接的形式。这不是过早的优化。 另一方面,这通常不足以让人们信奉这两者。

        【讨论】:

          【解决方案10】:

          这是一个老生常谈的话题,SO 包含大量关于它的好建议和坏建议。

          让我告诉你我从许多性能调优的经验中发现了什么。

          性能与记忆力和清晰度等其他因素之间存在权衡曲线,对吗?而且您会期望要获得更好的性能,您必须放弃一些东西,对吗?

          只有当程序在权衡曲线上时才是正确的。大多数软件,正如最初编写的那样,距离权衡曲线几英里。大多数时候,谈论放弃一件事以获得另一件事是无关紧要和无知的。

          我使用的方法不是测量,是diagnosis。我不在乎各种例程的速度或调用频率。我想确切地知道哪些指令导致缓慢,以及为什么

          大型软件工作(而不是小型单人项目)中表现不佳的主要原因和主要原因是泛滥。使用了太多的抽象层,每个抽象层都会带来性能损失。这通常不是问题 - 直到它成为问题 - 然后它是一个杀手。

          所以我所做的就是一次解决一个问题。我称这些为“蛞蝓”,是“慢虫”的缩写。我移除的每个 slug 都会产生 1.1 倍到 10 倍的加速,具体取决于它有多糟糕。每一个被移除的蛞蝓都会使剩余的蛞蝓占据剩余时间的大部分,因此它们变得更容易找到。如此一来,所有的“唾手可得的果实”都可以快速处理掉。

          那时,我知道是什么在浪费时间,但修复可能会更加困难,例如软件的部分重新设计,可能是通过删除无关的数据结构或使用代码生成。如果有可能做到这一点,那将掀起新一轮的清除程序,直到程序不仅比开始时快很多倍,而且更小、更清晰。

          我建议您自己获得这样的经验,因为这样当您设计软件时,您就会知道该做什么,并且您会从一开始就做出更好(和更简单)的设计。同时,您会发现自己与经验不足的同事发生了冲突,他们无法在不编出十几个课程的情况下开始思考设计。

          添加:现在,为了尝试回答您的问题,当诊断表明您有热点时应该进行低级优化(即调用堆栈底部的一些代码出现在足够的调用堆栈样本上(10%或更多)被认为要花费大量时间)。如果热点在代码中,您可以编辑。如果您在“新建”、“删除”或字符串比较中遇到热点,请在堆栈的更高位置查找要删除的内容。

          希望对您有所帮助。

          【讨论】:

          • 没用。这是一个非常好的答案,就像尼尔的一样,但这与我提出的问题无关。有关我正在寻找的更多信息,请参阅下面 jalf 的答案。
          • 对不起。我猜你点击了我的“通用”按钮。我遇到太多程序员,他们担心 ++i、编译器优化或虚函数速度等问题,而他们的设计导致 30 倍减速。
          • 没有问题。看起来我按下了很多“通用”按钮 :-) 如果我知道我会用不同的方式表达这个问题。
          • 关于 SO 的措辞问题是一门艺术。你的问题的措辞比我做过的几个要好得多,并且因此受到了抨击。
          【解决方案11】:

          是的,这些事情仍然相关。我做了相当多的这种优化,但公平地说,我主要编写的代码必须在 ARM9 上大约 10 毫秒内完成相对复杂的事情。如果您正在编写在更现代的 CPU 上运行的代码,那么好处不会那么大。

          如果您不关心可移植性,并且您正在做一些数学运算,那么您还可以考虑使用目标平台上可用的任何矢量运算 - x86 上的 SSE,PPC 上的 Altivec。没有大量帮助,编译器无法轻松使用这些指令,而且这些内部函数现在非常容易使用。您链接到的文档中未提及的另一件事是指针别名。如果您的编译器支持某种“限制”关键字,您有时可以获得很好的速度改进。另外,当然,考虑缓存的使用也很重要。与优化奇数副本或展开循环相比,以充分利用缓存的方式重新组织代码和数据可以显着提高速度。

          不过,与以往一样,最重要的是要进行剖析。仅优化实际上很慢的代码,确保您的优化实际上使其更快,并在尝试改进之前查看反汇编以了解编译器已经为您进行了哪些优化。

          【讨论】:

            【解决方案12】:

            不好的例子——决定是使用++i 还是i++ 不涉及任何形式的权衡! ++i 有(可能有)净收益没有任何缺点。有很多类似的场景,在这些领域进行任何讨论都是浪费时间。

            也就是说,我相信了解目标编译器能够优化小代码片段的能力到什么程度非常重要。事实是:现代编译器(有时令人惊讶!)擅长它。 Jason 有一个关于优化(非尾递归)阶乘函数的 incredible story

            另一方面,编译器也可能非常愚蠢。关键是许多优化需要一个控制流分析,它变成了 NP 完备的。因此,优化成为编译时间和实用性之间的权衡。通常,优化的局部性起着至关重要的作用,因为当编译器认为的代码大小仅增加几条语句时,执行优化所需的计算时间就会增加太多。

            正如其他人所说,这些微小的细节仍然是相关的,并且永远都是(在可预见的未来)。尽管编译器越来越聪明,机器越来越快,但我们的数据量也在增长——事实上,我们正在输掉这场特殊的战斗;在许多领域,数据量的增长速度远远超过计算机变得更好的速度。

            【讨论】:

            • ++ 我引擎盖中的蜜蜂是低级的,编译器优化并不重要,除非您正在编写一个真实的热点 - 即没有函数调用的循环需要花费大量时间。和算法?很好,但这还不够。真正的杀手是“一般”复杂性的层,导致你可怜的程序计数器无休止的嵌套边路鹅追逐。
            【解决方案13】:

            当然,当且仅当它会导致该特定程序的实际改进足以值得编码时间,任何可读性降低等。我认为你不能在所有程序中为此制定规则,或者真正为任何优化制定规则。这完全取决于在特定情况下真正重要的是什么。

            对于像 ++i 这样的东西,时间和可读性的权衡是如此之小,如果它真的能带来改进,那么它可能值得养成习惯。

            【讨论】:

              【解决方案14】:

              如果优化没有成本,那就去做吧。写代码的时候++ii++一样容易写,所以更喜欢前者。没有任何费用。

              另一方面,之后返回并进行此更改需要时间,而且很可能不会产生明显的差异,因此您可能不应该为此烦恼。

              但是,是的,它可以有所作为。在内置类型上,可能不是,但对于复杂的类,编译器不太可能将其优化掉。原因是自增操作不再是编译器内置的内在操作,而是类中定义的函数。编译器可能能够像任何其他函数一样对其进行优化,但一般来说,它不能假设可以使用前增量而不是后增量。这两个函数可能做完全不同的事情。

              所以在确定编译器可以进行哪些优化时,要考虑它是否有足够的信息来执行它。在这种情况下,编译器不知道后增量和前增量对对象执行相同的修改,因此它不能假设一个可以替换为另一个。但是你有这方面的知识,所以你可以安全地执行优化。

              您提到的许多其他内容通常可以由编译器非常有效地完成: 内联可以由编译器完成,而且它通常比你更擅长。它只需要知道函数中有多大比例是由函数调用构成的,以及它被调用的频率是多少?一个经常被调用的大函数可能不应该被内联,因为你最终会复制大量代码,从而导致更大的可执行文件和更多的指令缓存未命中。内联始终是一种权衡,而且编译器通常比您更擅长权衡所有因素。

              循环展开是一种纯粹的机械操作,编译器可以轻松做到这一点。强度降低也是如此。交换内循环和外循环比较棘手,因为编译器必须证明更改的遍历顺序不会影响结果,这很难自动完成。所以这里有一个你应该自己做的优化。

              但即使在编译器能够执行的简单操作中,您有时也会获得编译器没有的信息。如果你知道一个函数会被非常频繁地调用,即使它只是从一个地方调用,也值得检查编译器是否自动内联它,如果没有,则手动执行。

              有时您可能比编译器更了解循环(例如,迭代次数始终是 4 的倍数,因此您可以安全地展开 4 次)。编译器可能没有这些信息,所以如果要内联循环,它必须插入一个 Epilog 以确保最后几次迭代得到正确执行。

              因此,如果 1) 您确实需要性能,并且 2) 您有编译器不需要的信息,那么这种“小规模”优化仍然是必要的。

              在纯粹的机械优化上,您无法超越编译器。但是你可以做出编译器不能做的假设,而是你能够比编译器优化得更好的时候。

              【讨论】:

              • 我假设自动强度降低仅适用于原始类型。如果您正在对任意精度的数字或矩阵进行数学运算,请继续进行。
              【解决方案15】:
              • 我通常不会优化低于 O(f(n)) 复杂度,除非我在嵌入式设备上编写。

              • 对于典型的 g++/Visual Studio 工作,我认为基本的优化将会可靠地进行(至少在请求优化时)。对于不太成熟的编译器,这个假设大概是不成立的。

              • 如果我对数据流进行繁重的数学运算,我会检查编译器发出 SIMD 指令的能力。

              • 我宁愿围绕不同的算法调整我的代码,而不是针对特定编译器的特定版本。算法将经受住多个处理器/编译器的考验,而如果您针对 2008 Visual C++(第一版)版本进行调整,您的优化甚至可能在明年都无法奏效。

              • 某些在旧计算机中非常合理的优化技巧如今被证明存在问题。例如,++/++ 运算符是围绕旧架构设计的,该架构具有非常快的增量指令。今天,如果你做类似的事情

                for(int i = 0; i < top; i+=1)

                我假设编译器会将i+=1 优化为inc 指令(如果CPU 有的话)。

              • 经典建议是自顶向下优化。

              【讨论】:

                【解决方案16】:

                多年来我得到的一个有趣的观察是,上一代的优化代码似乎实际上在下一代反优化。这是因为处理器实现发生了变化,因此 if/else 成为现代 CPU 的瓶颈,其中管道很深。我想说干净、简短、简洁的代码通常是最好的最终结果。优化真正重要的地方在于数据结构,使它们正确且精简。

                【讨论】:

                  【解决方案17】:

                  我相信每个开发人员都应该知道关于优化的三句话——我首先在 Josh Bloch 的“Effective Java”一书中读到它们:

                  更多的计算罪发生在 效率名称(不含 必然实现它)比任何 其他单一原因 - 包括盲人 愚蠢。

                  (William A. Wulf)

                  我们应该忘记小 效率,说大约 97% 时间:过早优化是 万恶之源。

                  (Donald E. Knuth)

                  我们在这件事上遵循两条规则 优化:

                  规则 1:不要这样做。

                  规则 2:(仅适用于专家)。不要做 它还没有 - 也就是说,直到你有一个 完全清晰且未优化 解决方案。

                  (M. A. Jackson)

                  所有这些引用 (AFAIK) 至少有 20 到 30 年的历史,那时 CPU 和内存的意义远远超过今天。我认为开发软件的正确方法是首先有一个可行的解决方案,然后使用分析器测试性能瓶颈在哪里。一位朋友曾经告诉我一个用 C++ 和 Delphi 编写的应用程序,并且存在性能问题。他们使用分析器发现应用程序花费了大量时间将字符串从 Delphi 的结构转换为 C++ 结构,反之亦然 - 没有微优化可以检测到...

                  总之,不要以为你知道性能问题会出现在哪里。为此使用分析器。

                  【讨论】:

                    【解决方案18】:

                    首先 - 始终运行分析来检查。

                    首先,如果您优化代码的正确部分。如果代码运行总时间的 1% - 算了。即使您将其加速了 50%,您也可以获得 0.5% 的总加速。除非您正在做一些奇怪的事情,否则加速会慢得多(尤其是如果您确实使用了良好的优化编译器)。 其次,如果您正确优化它。哪个代码在 x86 上运行得更快?

                    inc eax
                    

                    add eax, 1
                    

                    嗯。据我所知,在早期的处理器中是第一个,但在 P4 上是第二个(如果这些特定指令运行得更快或更慢,这里无关紧要,关键是它一直在变化)。编译器可能会与此类更改保持同步 - 您不会。

                    在我看来,主要目标是编译器无法执行的优化 - 如前所述,数据大小(您可能认为现在 2 GiB 计算机不需要它 - 但如果您的数据比处理器缓存大 -它会运行得更慢)。

                    一般情况下 - 只有在您必须和/或您知道自己在做什么时才这样做。这将需要大量关于问题中未提及的代码、编译器和低级计算机体系结构的知识(老实说——我没有提出)。它很可能一无所获。如果你想优化 - 在更高的层次上进行。

                    【讨论】:

                      【解决方案19】:

                      当然可以,因为编译器需要更多资源来优化未优化的代码,而不是优化已经优化的代码。特别是它会导致计算机消耗更多的能量,尽管它很小,但仍然对已经受到伤害的自然造成不良影响。这对于开源代码尤其重要,因为它比闭源代码更容易被编译。

                      走向绿色,拯救地球,优化自己

                      【讨论】:

                        【解决方案20】:

                        不要试图猜测你的编译器在做什么。如果您已经确定需要在此级别优化某些内容,请隔离该位并查看生成的程序集。如果您可以看到生成的代码正在执行一些可以改进的缓慢操作,请务必在代码级别对其进行修改,看看会发生什么。如果您确实需要控制,请在汇编中重写该位并将其链接。

                        这很让人头疼,但这是真正了解正在发生的事情的唯一方法。请注意,一旦您更改任何内容(不同的 CPU、不同的编译器、甚至不同的缓存等),所有这些严格的优化都可能变得毫无用处,这是一种沉没成本。

                        【讨论】:

                          【解决方案21】:

                          还需要注意,从前/后递增/递减运算符进行更改不会引入不良副作用。例如,如果您在循环上迭代 5 次只是为了多次运行一组代码而对循环索引值没有任何兴趣,那么您可能没问题 (YMMV)。另一方面,如果您确实访问了循环索引值,则结果可能不是您所期望的:

                          #include <iostream>
                          
                          int main()
                          {
                            for (unsigned int i = 5; i != 0; i--)
                              std::cout << i << std::endl;
                          
                            for (unsigned int i = 5; i != 0; --i)
                              std::cout << "\t" << i << std::endl;
                          
                            for (unsigned int i = 5; i-- != 0; )
                              std::cout << i << std::endl;
                          
                            for (unsigned int i = 5; --i != 0; )
                              std::cout << "\t" << i << std::endl;
                          }
                          

                          结果如下:

                          5
                          4
                          3
                          2
                          1
                                  5
                                  4
                                  3
                                  2
                                  1
                          4
                          3
                          2
                          1
                          0
                                  4
                                  3
                                  2
                                  1
                          

                          前两种情况没有区别,但请注意,尝试通过切换到预减运算符来“优化”第四种情况会导致迭代完全丢失。诚然,这有点做作,但我在以相反顺序(即从头到尾)遍历数组时看到了这种循环迭代(第三种情况)。

                          【讨论】:

                          • 我知道你的观点是正确的,但你可能不是唯一一个还在谈论 ++i 或 i++ 是否更快的人,在一个包含std::cout 的循环中:-)
                          • ...我刚刚拍摄了您的代码的 5 个堆栈快照。一个在operator&lt;&lt; -&gt; writepad -&gt; strlen。其他 4 个在 operator&lt;&lt; -&gt; endl -&gt; operator&lt;&lt; -&gt; flush -&gt; ostream::flush -&gt; filebug::sync -&gt; _write -&gt; KERNEL32! -&gt; KERNEL32! -&gt; KERNEL32! -&gt; NTDLL! 中,至少到目前为止,--i 运营商并未受到关注。
                          • @Mike Dunlavey:我们在这里谈论两个不同的事情。我的观点是关于盲目地将后递增和后递减运算符分别更改为预递增和预递减运算符的潜在副作用。我并没有声称哪些运算符更快。
                          【解决方案22】:

                          我仍然做 ra

                          尽管性能的底线从未改变。找到一些方法来计时你的代码,测量以找到低悬的果实并修复它。 Mike D. 在他的回应中一针见血。我有太多次看到人们担心特定的代码行没有意识到他们要么使用了一个糟糕的编译器,要么通过更改一个编译器选项他们可以看到执行性能提高了几倍。

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2018-10-26
                            • 1970-01-01
                            • 2021-07-04
                            • 2014-01-06
                            • 2017-01-23
                            • 2020-04-07
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多