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;
}

现在,当我从头开始编写时,我会习惯性地应用自己的“优化”,但我知道编译器也会执行以下操作:

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

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

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

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

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

3 回答 3

16

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

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

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

上述建议适用于任何优化。但是,最后一点与低级优化特别相关。它们有点像魔法,因为涉及到很多相关的架构细节:内存层次结构和带宽、指令流水线分支预测SIMD指令的使用等。

我认为依赖编译器编写者对目标体系结构有很好的了解比试图超越他们更好。

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

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

于 2013-03-23T08:34:56.047 回答
0

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

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

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

找到热点后,解决方案可能涉及:

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

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

更多细节,你可以谷歌代码优化和类似术语。

如果你想认真一点,你还应该看看Agner Fog 的优化手册Computer Architecture: A Quantitative Approach。确保获得最新版本。

您可能还想阅读微优化剧院的悲惨悲剧

于 2013-03-23T13:33:11.943 回答
0

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

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

现代编译器有各种策略来优化你的代码,尤其是当你在做并发编程,并使用像OMPBoostTBB这样的库时。

如果您确实关心您的代码究竟是什么变成了机器代码,那么反编译并观察程序集也不会更好。

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

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

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

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

于 2013-03-23T09:50:15.387 回答