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

9
我希望了解如何在将遗留代码、库代码或示例代码集成到自己的代码库中时,避免重复编写源代码浪费时间并冒错的风险。
举个简单的例子,假设有一个图像处理方案,你可能会明白我的意思。
事实上,在集成代码片段时,像这样的情况并不少见:
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
@john - 是的,了解代码是一个好点子,风格也是,我没有提到。我的主要问题是,我找不到任何关于例如VS2012在优化方面做了什么的文档。 - Roger Rowland
在Google上搜索免费的PDF书籍 - "Optimizing software in C++ An optimization guide for Windows, Linux and Mac platforms",作者是Agner Fog。保证这将成为您周末最喜欢的阅读材料。 - SChepurin
3个回答

16
我认为你提供的优化示例大多数都没有意义。优秀的优化编译器应该能够替你完成所有这些工作。
以下是三条实用建议:
1.在真实数据处理应用程序的情况下对您的代码进行分析。如果不行,可以设计一些合成测试,以模拟最终系统。
2.只优化通过分析已证明是瓶颈的代码。
3.如果您确信某段代码需要优化,请不要仅仅假设将循环中不变的表达式分解会改善性能。请始终进行基准测试,可以选择查看生成的汇编代码以获取更深入的了解。
上述建议适用于任何优化。然而,最后一点特别与低级优化相关。它们有点像黑魔法,因为涉及许多相关的架构细节:内存层次结构和带宽、指令流水线, 分支预测, 使用SIMD指令等。
我认为更好地依赖于编译器编写者对目标架构有良好的了解,而不是试图超越他们。
有时,通过分析你会发现需要手动优化事物。然而,这些情况将相当罕见,这将使你花费大量精力在实际上会有所作为的事物上。
同时,专注于编写正确和可维护的代码。

1
我必须同意,我的理解也是,在此发布时列出的所有优化都将被任何体面的、流行的优化编译器所识别。这可能有助于调试构建,但在最终输出上没有任何区别。 - Sion Sheevok
4
关于你的第一条评论,除非你能够进行剖析,否则不要做任何一个。在最初的情况下,应该专注于正确性和清晰度。 - NPE
我不确定是否有详尽的列表,但如果有疑问,可以两种方式都写一下,并查看汇编输出 - 如果在发布时它们相同,那么编译器已经应用了优化。你不一定需要能够理解汇编输出,就能看出它们是相同的。 - Sion Sheevok
@NPE - 是的,我明白。我在问题中提到了我意识到过早优化的问题。我真的希望能找到一些关于我使用的编译器的信息链接 - 对于混淆感到抱歉。 - Roger Rowland
3
@roger_rowland,您可能知道过早优化的存在,但似乎不知道您正在问如何进行过早优化... - rubenvb
显示剩余4条评论

0
关于 C/C++ 编译器优化,我可以做出什么假设呢?
尽可能地想象吧,除非你在优化后的代码中遇到了功能或性能问题,那么就关闭优化并进行调试。
现代编译器有各种策略来优化你的代码,特别是当你进行并发编程并使用像 OMP、Boost 或 TBB 这样的库时。
如果你真的关心你的代码被转换成机器码的情况,最好的方法是反编译它并观察汇编代码。
对于你来说最重要的事情是进行手动优化,可能需要减少不可预测的分支,这是编译器难以完成的。

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

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

还有一些关于优化策略和技术的内容


我不同意。编译器并非魔法。自动程序转换的能力有其实际和理论上的限制。你所举的例子是相对简单的转换,确实已经存在了几十年。然而,除了一些特定的语言(即:不包括C++或类似语言)和微不足道的情况外,没有编译器可以修复糟糕的算法或误用的数据结构。参见:http://www.joelonsoftware.com/articles/fog0000000319.html - user395760
好的,非常感谢——这正是我在寻找的。 我似乎通过使用特定示例来说明我的问题,把人们引入了错误的方向。 但您确实提供了我想要的链接,我没有找到之前的SO问题,我猜我的问题是那个的复制。 - Roger Rowland
@roger_rowland:不用谢。但是似乎NPE的回答得到了更多人的认同,也许应该接受他的回答。 - Ken Kin
@delnan:实际上,循环不变式代码移动可以将一些O(N*M)算法转化为O(N)+O(M)算法。同样,你链接的可怕的strcat()算法也是可优化的,只需注意它从未缩短字符串即可。这仍然会重新扫描新添加的部分,但O(2N) == O(N)。(你需要C99来使用restrict,C++语法无法表达src和dest不重叠的限制) - MSalters
@MSalters 因此,“对于[...]微不足道的情况有例外”的意思。此外,针对strcat的优化需要编译器看到定义(与C stdlib的动态链接不兼容,这是非常常见的),或者具有内置函数的特殊知识。后者对于大多数糟糕算法的情况并没有帮助,因为通常问题不仅在于如何调用标准库函数。 - user395760

0

我认为重新考虑你问题的前提条件,而不是直接回答问题可能更有用。

你为什么想要进行这些优化呢?根据你的问题,我猜想你是想让一个具体的程序更快。如果是这样,你需要从以下问题开始:如何让这个程序更快?

这个问题有一个非常不同的答案。首先,你需要考虑Amdahl's law。通常意义上,只有优化一个或两个重要部分才有意义。其它所有部分基本上都没有关系。你应该使用分析工具来定位这些程序部分。此时,你可能会争辩说你已经知道你应该使用分析工具了。然而,我认识的几乎所有的程序员都不会对他们的代码进行分析,即使他们知道他们应该这样做。知道蔬菜是什么并不等于吃蔬菜。;-)

一旦你找到热点,解决方案很可能会涉及以下内容:

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

再次强烈建议您使用分析器来查看更改后的运行时间是否有所改善。

如需了解更多详情,您可以搜索代码优化等相关术语。

如果您想要更深入地了解,请参考Agner Fog的优化手册计算机体系结构:量化研究方法。请务必获取最新版本。

您还可以阅读微优化剧场的悲哀悲剧


好的,我已经尽力解释问题了,但似乎比我预期的要困难一些;-) 我不想知道要优化什么,我想知道作为正常实践而言,不需要费心去优化什么。 - Roger Rowland
1
那么答案几乎就是一切。但我明白这是一个合理的问题。 - Jørgen Fogh
有很多关于GCC优化的丰富列表的资料可供使用。您可以通过查找GCC的单个每个优化开关,然后搜索相关主题来了解更多信息。 - Cecil Ward
通过编写小的C和D程序,编译它们,然后使用GCC的-O2-O2开关查看生成的代码,我学到了很多关于现代编译器优化能力的令人印象深刻的状态。我知道您正在使用MSVC,但在该编译器中也会有查看生成代码的选项,并且在MSVC上打开完全优化开关生成的代码质量可能与GCC一样丰富。请参见https://gcc.godbolt.org。(如果您想尝试新东西,也可以查看http://d.godbolt.org。) - Cecil Ward

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接