【问题标题】:Code execution slowing due to header include由于标头包含导致代码执行速度变慢
【发布时间】:2012-09-24 10:34:18
【问题描述】:

我们这里有一个非常大的程序,它混合了 C++ 和 FORTRAN(抱歉)。我的一次签入导致整个应用程序的速度急剧下降(即两倍或更多)——即使在不受我的更改影响的代码区域也是如此。

事实:

  1. 几乎每个模块都减慢了相似的速度 - 即使是那些不使用我的代码的模块。

  2. 可执行文件大约大 6%。

  3. 在签入之间元数据未更改。

  4. IDE/编译器是VS2010的Release模式。

  5. 一些 .lib 文件的大小增加了一倍或三倍。

我查看了一个大小增加了两倍的 .lib 文件,发现只有两个变化:

a) 我已经包含了一个很大的头文件,该头文件又包含许多其他头文件——其中一些包含中等复杂的内联代码。 “其他包含目录”已从无或一变为大约 7 个,因为每个头文件#includes 一个或多个其他。

b) 我从这个头文件中调用了 4 个函数,但是在运行速度变慢的过程中没有调用这些函数(即它们的 执行 不能减慢代码速度,但是它们的 inclusion 可能是)。

尽管搜索了论坛关于是否包含头文件会减慢执行(与编译相反),但我找不到一篇相关文章。我的问题是:

any 形式的标头(声明或内联)的#inclusion 是否会减慢代码执行

?包含 inline 代码 w.r.t. 是否存在定性或定量差异?执行速度(我知道“内联”只是对编译器的建议)?

? .lib 大小、.exe 大小和执行速度之间有什么相关性(我预计这里会有很多不同和矛盾的相关性)?

?重构一些头文件以使其不需要包含其他头文件(通过将这些包含放入 .cpp 文件中,从而减少我的“其他包含目录”)会改善我的情况吗?

我想最后一个问题是问题的核心,因为这需要付出很多努力......

【问题讨论】:

  • 找一个好的分析器,执行代码,收集每次调用的时间信息,分析然后决定做什么。听起来你是在黑暗中拍摄。
  • 如果这是构建时问题,分析不是最有效的途径。如果确实是它而不是其他任何东西导致了这种变化,我建议关注检查本身的内容。
  • 对不起:应该说我们已经分析了之前和之后的案例。这就是我们知道它不是我的新功能之一的方式,因为它们几乎没有被调用。所有现有的代码单元都运行得更慢......

标签: c++ linker profiling compiler-optimization


【解决方案1】:

更改头文件不会更改执行时间,除非您意外地将构建 DEBUG 或其他诊断代码的内容包含到生成的二进制文件中。

这将是我的猜测,尤其是。考虑到输出文件大小的变化。

【讨论】:

  • 好的,所以如果我发疯并链接(通过链式#includes)1000 个包含大量复杂内联代码的头文件,我实际上并没有调用,这会 a) 增加 . exe 大小但对速度没有影响,或者 b) 不增加 .exe。 size 提高速度(因为编译器意识到它可以全部忽略)?
  • #pragma comment(lib...) 可能会引入大量代码,其中包含 1000 个头文件,这可能会继续进行。大小会增加,即使你不调用这些函数,程序的其他部分现在可能会调用它们(而不是来自不同的更快的库,只是猜测)
  • 你可以编译成一个预解析文件(我认为是-P),然后浏览巨大的结果文件,看看预处理器到底在做什么
  • 现在听起来一点也不好玩!
  • 我们没有在这段代码中使用#pragma comment(lib 语句——但这会导致更多的代码被拉进来吗?这不是有效地增加了正常的链接吗?
【解决方案2】:
  • #inclusion 是否包含任何形式的标头(声明或内联)会减慢代码执行速度? 好吧,如果您的应用必须

    这只改变了代码出现的地方,所以我相信它不会改变任何东西。 (如果你只是将一个函数向上移动三行,你的代码不会变得更快/更慢,对吗?)

  • 在包含内联代码 w.r.t. 方面是否存在定性或定量差异?执行速度(我知道“内联”只是对编译器的建议)

    也许吧。如果你比较一个内联函数和非内联函数,内联函数可能会更快,因为它的代码只是复制粘贴到适当的地方,而普通的函数调用会浪费一些时间。

  • .lib 大小、.exe 大小和执行速度之间的相关性是什么(我预计这里会有很多不同和矛盾的相关性)

    虽然我可以想象一个较大的文件会降低速度的假设情况,但我敢说大多数时候没有相关性。

您的可执行文件可能更大,因为您可能覆盖了一些影响执行的宏(例如取消定义旨在排除某些代码的定义)。这也可能导致性能下降(即,您不希望执行某些代码,但由于意外的宏重新定义它这样做了)。

【讨论】:

  • 我认为这是我不喜欢在头文件中包含所有代码的原因之一 - 这意味着您可能会无意中包含不愉快的#defines,例如可怕的 重新定义“min”和“max”。但是,这是您认为的唯一风险?
【解决方案3】:

你在使用 COM 吗?您的包含文件是否将 STA 更改为 MTA 或反之亦然?您的包含文件现在是否拉入在您进行动态链接(lib pragma)之前的库中?是否包含不再拉入库并且您的代码不再动态链接?我重复史蒂夫的,是否包含调试库?

DUMPBIN 可能会让您更深入地了解实际构建的内容。将结果与旧版本进行比较,看看是否有什么突出的地方。

附加编辑: 检查测试机器上的内存使用情况,检查分页活动,如果您的较大 exe 已超过阈值。

【讨论】:

  • 我们已经检查了 Debug 构建,并且我们相当确定它们都没有在 Debug 中构建。我们没有使用 COM 或任何奇异的东西 ;-) - 只是 VC++ 和英特尔 FORTRAN。不过,我 am 延迟加载 dll - 尽管在这种情况下没有使用它,并且它与大多数运行缓慢的代码无关。我会调查 DUMPBIN...
【解决方案4】:

#inclusion 是否包含任何形式的标头(声明或内联)会减慢代码执行速度?

添加未使用的声明或添加未使用的内联定义不会减慢执行速度。然而,我可以想象有几件事情会减慢执行速度:

  • 一些#define 会阻止常用函数的优化内联或宏变体由另一个标题提供。
  • 某些常用操作的重载,可能来自标准库,效率低于默认值。

在包含内联代码 w.r.t. 方面是否存在定性或定量差异?执行速度(我知道“内联”只是对编译器的建议)?

好吧,如果代码不可用,就不能内联。如果是,那它可以。通常编译器可以估计内联会节省多少,如果没有帮助就不会内联,但有时它可能会猜错。在这种情况下,结果将与通常情况下略有帮助的情况大不相同。

.lib 大小、.exe 大小和执行速度之间有什么相关性(我预计这里会有很多不同和矛盾的相关性)?

情况完全不同。内联会增加代码大小,但可以在每个调用站点上节省大量工作。但是较大的代码会占用更多的缓存,这会减慢速度。

重构一些头文件,使其不需要包含其他头文件(通过将这些包含放入 .cpp 文件中,从而减少我的“其他包含目录”)改善我的情况,你认为吗?

它可能会也可能不会。视实际原因而定。

我建议你真的应该尝试找出原因。它几乎肯定是由某些特定的代码引起的,而不是包含的代码量。所以回到更改之前的修订版并一点一点地添加包含的内容。首先单独包含最里面的标题,然后逐个添加使用它们的标题等等。当您到达使事情变得更糟的特定标头时,请尝试注释掉它的一些部分,直到将其缩小到特定声明或其中的几个。

也只去掉一些你观察到性能下降的函数。如果您缩小范围但仍然看不到可能出现的问题,那么您将拥有一些其他人可以重现该问题的内容,因此您可以将其用作新问题。

【讨论】:

  • 感谢所有提供帮助的人 - 我已将此标记为答案,不一定是因为它解决了我的特定问题,而是因为我认为它最好地解决了阅读此线程的任何人的各个问题未来。
  • 关于重载的一个说明:我们有一个包含许多项目的大型解决方案。如果一个项目多年来一直包含来自“foo.lib”的函数“void foobar()”,然后另一个项目开始包含来自“bar.lib”的“void foobar()”,两个项目最终使用相同foob​​ar() 在最终的 .exe 中?
  • 链接顺序将决定
  • 所以如果.exe项目要链接的第一个项目包括foo.lib,后面的项目包括bar.lib,就会使用foo.lib->foobar()。这似乎是一个很大的风险——为什么链接器不抱怨这个?它抱怨其他一切......
  • @MikeSadler:对于大多数 unix 链接器,链接顺序将决定选择哪个版本。这将是链接器遇到的第一个。但是 MSVC++ 应该会出错,如果你告诉它使用/FORCE 选项选择第一个,它仍然应该给出警告。但是如果另一个项目开始在标题中定义void foobar() inline,那么你最终会遇到致命的混合。
【解决方案5】:

盲射:

这可能是缓存问题。内联函数并将“死”代码添加到库中会导致代码变大,并且会增加程序执行期间的缓存未命中次数。

您可以通过简单地监控进程执行期间的缓存未命中次数来查看这是否是正确的路径。


关于您的评论:

6% 是多少?

如果您的 L1 缓存溢出(据我所知,即使在现代处理器上,它的大小也约为 32K),您需要将 L1 访问换成 L2 访问,这会慢 2 倍左右。 如果您溢出 L2 缓存(范围从 256K 到 2M)并开始访问 L3,则获取页面的速度会再降低 5 倍(您可以查看this question,它给出了核心 i7 的数据)。

以下是关于wikipedia 上的缓存未命中的一般说明。

再一次,要查看这是否真的是问题所在,您应该监控进程在执行期间命中的缓存未命中次数(我认为 process explorer 向您展示了这一点,如果您使用的是 Windows,或者 perf 如果您使用正在使用linux)

【讨论】:

  • 我猜有两个问题:1) .exe 大小增加 6% 会导致速度降低 50% 吗? (我可能猜想较大的 .exe 可能会略微降低速度...)。 2) 是否有定义的 .exe 大小阈值可能导致性能的阶跃变化? (例如 32Mb、64Mb 等)
【解决方案6】:

猜测不太可能发现问题。诊断就会发现。 如果减速是两倍,这意味着较慢的代码花费了 50% 的时间来做一些以前没有做的不必要的事情。 这很容易找到。

This 是我使用的方法,因为它确实找到了问题。 Here's 一个例子,here's 一堆原因。

我建议您首先在 un 优化的代码中诊断问题。 然后,当您清除问题时,打开优化器并让它完成它的工作。 很有可能代码包含您可以修复而优化器无法修复的重大问题。 不管优化器做什么,它都不会让你更容易找到只有你能解决的问题。

【讨论】:

    猜你喜欢
    • 2023-03-16
    • 1970-01-01
    • 2019-01-15
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 2013-03-24
    • 2021-12-14
    • 2014-06-02
    相关资源
    最近更新 更多