【问题标题】:What can I assume about C/C++ compiler optimisations?关于 C/C++ 编译器优化,我可以假设什么?
【发布时间】:2013-03-13 03:55:59
【问题描述】:

当我将遗留代码、库代码或示例代码集成到我自己的代码库中时,我想知道如何通过重新散列源代码来避免浪费我的时间和冒着输入错误的风险。

如果我给出一个基于图像处理场景的简单示例,您可能会明白我的意思。

发现我正在集成这样的代码 sn-p 实际上并不罕见:

for (unsigned int y = 0; y < uHeight; y++)
{
    for (unsigned int x = 0; x < uWidth; x++)
    {
        // do something with this pixel ....
        uPixel = pPixels[y * uStride + x];
    }
}

随着时间的推移,我已经习惯于将不必要的计算移出内部循环,并可能将后缀增量更改为前缀......

for (unsigned int y = 0; y < uHeight; ++y)
{
    unsigned int uRowOffset = y * uStride;
    for (unsigned int x = 0; x < uWidth; ++x)
    {
        // do something with this pixel ....
        uPixel = pPixels[uRowOffset + x];
    }
}

或者,我可以使用指针算术,或者按行...

for (unsigned int y = 0; y < uHeight; ++y)
{
    unsigned char *pRow = pPixels + (y * uStride);
    for (unsigned int x = 0; x < uWidth; ++x)
    {
        // do something with this pixel ....
        uPixel = pRow[x];
    }
}

...或按行和列...所以我最终得到了这样的结果

unsigned char *pRow = pPixels;
for (unsigned int y = 0; y < uHeight; ++y)
{
    unsigned char *pPixel = pRow;
    for (unsigned int x = 0; x < uWidth; ++x)
    {
        // do something with this pixel ....
        uPixel = *pPixel++;
    }

    // next row
    pRow += uStride;
}

现在,当我从头开始编写时,我会习惯性地应用自己的“优化”,但我知道编译器也会做如下事情:

  • 将代码从内部循环移动到外部循环
  • 将后缀增量更改为前缀
  • 还有很多我不知道的东西

请记住,每次我以这种方式处理一段工作、经过测试的代码时,我不仅会花费自己一些时间,而且还会冒着引入手指问题或其他错误的风险(以上例子被简化)。我知道“过早的优化”以及通过设计更好的算法等来提高性能的其他方法,但是对于上述情况,我正在创建将在更大的流水线类型的应用程序中使用的构建块,我可以'无法预测非功能性需求可能是什么,所以我只希望代码在时间限制内尽可能快和紧凑(我的意思是我花在调整代码上的时间)。

所以,我的问题是:我在哪里可以找到“现代”编译器通常支持的编译器优化。我正在使用 Visual Studio 2008 和 2012 的混合版本,但有兴趣知道与替代品是否存在差异,例如英特尔的 C/C++ 编译器。谁能提供一些见解和/或指出有用的网络链接、书籍或其他参考资料?

编辑
只是为了澄清我的问题

  • 我上面展示的优化只是简单的例子,不是完整的列表。我知道进行这些特定更改是没有意义的(从性能的角度来看),因为无论如何编译器都会这样做。
  • 我正在专门寻找有关我正在使用的编译器提供了哪些优化的信息。

【问题讨论】:

  • 我不会做你建议的任何优化(除非代码真的很差),但我会解决其他“风格”问题。您没有提到的一个重要考虑因素是,通过这样的过程,您确实可以很好地了解您正在调整的代码。
  • @john - 是的,了解代码是一个好点,风格也是一个好点,我没有提到。我的主要问题是我找不到任何确切的文档,例如VS2012确实在优化的方式上。
  • Google 的免费 PDF 书籍 - “Optimizing software in C++ An optimization guide for Windows, Linux and Mac 平台” 作者 Agner Fog。保证,这将是你周末最喜欢的读物。

标签: c++ visual-c++ compiler-optimization


【解决方案1】:

我希望您作为示例包含的大多数优化都是浪费时间。一个好的优化编译器应该能够为您完成所有这些工作。

我可以通过实用的建议提供三个建议:

  1. 在处理真实数据的真实应用程序的上下文中分析您的代码。如果做不到,请提出一些您认为可以非常接近最终系统的综合测试。
  2. 仅优化您通过分析证明是瓶颈的代码。
  3. 如果您确信某段代码需要优化,请不要仅仅假设将不变量表达式分解出循环会提高性能。始终进行基准测试,可选择查看生成的程序集以获得进一步的洞察力。

上述建议适用于任何优化。但是,最后一点与低级优化特别相关。它们有点像魔法,因为涉及到很多相关的架构细节:memory hierarchy 和带宽、instruction pipeliningbranch predictionSIMD 指令的使用等。

我认为依靠对目标架构有深入了解的编译器编写者比试图智取他们更好。

有时您会通过分析发现您需要手动优化事物。但是,这些情况相当少见,这将使您能够将大量精力花在真正能产生影响的事情上。

同时,专注于编写正确且可维护的代码。

【讨论】:

  • 谢谢 - 我以为我很清楚缺乏特定的非功能性需求,但可能还不够清楚。在我的情况下,我可以在分析和找到瓶颈之前将很多这些“构建块”放在一起,所以我正在寻找“经验法则”列表 - 就像上面一样 - 我不应该费心去做。
  • 不得不同意,我的理解是,在本文发布时列出的所有优化都将被任何体面的、流行的优化编译器识别。可能有助于调试构建,但除此之外,最终输出没有区别。
  • @Sion - 是的,我只是将这些用作示例,我没有详尽的清单,这确实是我问题的重点。
  • @roger_rowland:至于你的第一条评论,在你能分析之前不要做任何。首先,关注正确性和清晰性。
  • @roger_rowland 您可能知道过早优化的存在,但您似乎没有意识到您在询问如何执行过早优化...
【解决方案2】:

我认为重新考虑问题的前提可能对您更有用,而不是直接得到答案。

为什么要执行这些优化?从你的问题来看,我认为这是为了更快地制作一个具体的程序。如果是这种情况,您需要从这个问题开始:如何让这个程序更快?

这个问题有一个非常不同的答案。首先,您需要考虑Amdahl's law。这通常意味着优化程序的一两个重要部分才有意义。其他一切都几乎无关紧要。您应该使用分析器来定位程序的这些部分。此时您可能会争辩说您已经知道应该使用分析器。然而,我认识的几乎所有程序员都不会分析他们的代码,即使他们知道应该这样做。了解蔬菜不等于吃蔬菜。 ;-)

一旦找到热点,解决方案可能包括:

  1. 改进算法,减少代码工作量。
  2. 改进内存访问模式,以提高缓存性能。

同样,您应该使用分析器查看您的更改是否改善了运行时间。

更多详情,您可以谷歌code optimization和类似术语。

如果你想认真一点,还应该看看Agner Fog's optimization manualsComputer Architecture: A Quantitative Approach。确保获得最新版本。

您可能还想阅读The Sad Tragedy of Micro-Optimization Theater

【讨论】:

  • 好吧,我已尽力解释问题是什么,但这似乎比我预期的要难 ;-) 我不想知道 什么优化,我想知道什么是不打扰作为正常做法进行优化。
  • 那么答案几乎就是一切。但我知道这是一个有效的问题。
  • 关于 GCC 提供的丰富的优化列表有很多可用的东西。您可以通过查找 GCC 的各个优化开关然后谷歌搜索主题来阅读此内容。
  • 通过编写小型 C 和 D 程序、编译它们,然后使用 GCC 的 -O2-O2 查看生成的代码,我学到了很多关于现代编译器优化能力令人印象深刻的状态开关。我意识到您正在使用 MSVC,但也可以选择在该编译器中查看生成的代码,并且为 MSVC 打开完全优化开关生成的代码质量可能与 GCC 一样丰富。见gcc.godbolt.org。 (如果你想尝试新的东西,也可以看看d.godbolt.org。)
【解决方案3】:

关于 C/C++ 编译器优化,我可以假设什么?

尽您所能,除非您遇到优化代码的功能或性能问题,然后关闭优化和调试。

现代编译器有多种策略来优化你的代码,特别是当你在做concurrent programming,并使用像OMPBoostTBB这样的库时。

如果你确实关心你的代码究竟是什么变成了机器代码,那么反编译它并观察程序集就再好不过了。

手动优化对你来说最重要的事情可能是减少不可预知的分支,编译器更难做到这一点。

如果你想查找关于优化的信息,已经有一个关于 SO 的问题

在优化选项中,有关于每个优化的解释:

还有一些关于优化策略和技术的东西

【讨论】:

  • 我不同意。编译器并不神奇。自动程序转换可以做的事情是有限制的,无论是实际的还是理论上的。您的示例是确实发生的相对简单的转换(并且已经发生了几十年)。但是,除了非常特殊的语言(阅读:不是 C++ 或类似的语言)和琐碎的情况外,没有编译器会修复 terrible algorithm 或误用的数据结构。
  • 好的,非常感谢 - 这比我想要的更多,我似乎通过使用一个具体的例子来说明我的问题,导致人们走错了路。但你确实提供了我想要的链接,我没有找到之前的 SO 问题,我猜我的问题是重复的。
  • @roger_rowland:不客气。但似乎 NPE 得到了更多人同意的回答,也许可以接受。
  • @delnan:实际上,循环不变的代码运动可以将一些 O(N*M) 算法变成 O(N)+O(M) 算法。同样,您链接到的糟糕的strcat() 算法也是可优化的,只需注意它永远不会缩短字符串。那仍然会重新扫描新附加的部分,但是 O(2N) == O(N)。 (你可能需要 C99 和 restrict,不过,C++ 语法不能表达 src 和 dest 不重叠的限制)
  • @MSalters 因此“除了 [...] 琐碎案例”。此外,strcat 的优化将需要编译器查看定义(与非常常见的 C 标准库的动态链接不兼容)或有关内置函数的特殊知识。后者对大多数糟糕的算法没有帮助,因为通常问题不仅仅在于标准库函数的调用方式。
猜你喜欢
  • 1970-01-01
  • 2020-09-20
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 2011-02-13
  • 2010-12-13
  • 2012-02-09
相关资源
最近更新 更多