【问题标题】:Header inclusion optimization标头包含优化
【发布时间】:2010-12-24 13:49:07
【问题描述】:

有没有一种自动的方法来优化 C++ 中头文件的包含,从而缩短编译时间? “自动”一词是指工具或程序。是否可以找到哪些头文件已过时(例如未使用公开的功能)?

编辑:让每个包含标题“只包含一次”是一件重要的事情,但是有没有办法甚至更改文件的内容,以便经常使用的“功能”在特定包含上而不是更少经常使用的功能在其他包含上?我要求太多了吗?不幸的是,我们正在谈论一个包含数千个文件的现有代码库。它会是我实际要求的重构工具吗?

谢谢。

【问题讨论】:

  • 您是从头开始,还是尝试优化现有代码库的标头?
  • 有一个现有的代码库,大约 3000 个源文件。
  • 你试过预编译头文件吗? IDE(Visual Studio 或 XCode)广泛使用它们来预编译所有 UI 和系统内容,因此每次都构建它们。
  • 这个问题对检查未使用的包含以及可以转发声明的类的工具有一些建议:stackoverflow.com/questions/1301850/…
  • Visual Assist X 是否以与 Resharper 相同的方式执行此操作?

标签: c++


【解决方案1】:

更新

我认为您真正想要的是“包含您使用的内容”,而不是一组最小的标题。 IWYU 表示尽可能前向声明,并包含直接声明您使用的符号的标头。您不能盲目地将文件转换为 IWYU 清洁文件,因为它可能不再编译。发生这种情况时,您需要找到丢失的标题并添加它。但是,如果每个文件都是 IWYU 清洁的,那么即使您必须偶尔添加标题,您的编译也会是 faster overall。更何况你的标题会更多meaningful/self-documenting

正如我之前的回答所指出的,在技术上可以包含比 IWYU 所需的更少的标题,但这通常是浪费时间。

现在,如果有一个工具可以为您完成大部分 IWYU 重构工作:)


我曾经考虑过创建/使用这样的工具。这个想法是使用二进制搜索和重复编译来找到最小的包含集。经过进一步调查,它似乎没有那么有用。

一些问题:

  • 更改包含的头文件可以改变行为,并且仍然允许文件编译。特别是一个示例,如果您在单独的头文件中定义了自己的std::swap。您可以删除该标头,您的代码仍将使用默认的 std::swap 实现进行编译。但是,std::swap 可能是:效率低下,导致运行时错误,或者更糟的是产生微妙的错误逻辑。

  • 有时头文件包含作为文档。例如,使用std::foreach,通常包括<vector> 就足以让它编译。代码加上额外的#include <algorithm> 更有意义。

  • 最小编译集可能无法在编译器或编译器版本之间移植。再次使用std::foreach 示例,不能保证std::foreach 将由<vector> 提供。

  • 无论如何,最小的包含集可能不会显着影响编译时间。 Visual Studio 和 gcc 支持#pragma once,这使得重复包括基本上不存在的性能明智。而且至少 gcc 的预处理器已经过优化,可以非常快地处理包含守卫(与 #pragma once 一样快)。

【讨论】:

  • 我是#pragma once的忠实粉丝
  • 我认为前向声明也有潜在的巨大收益,不包括。
  • @T.E.D.:在#pragma once(或任何编译指示)可用之前很久,人们就已经在使用#include 守卫了。几年前我参与了一个没有这两个项目的项目,这真的很痛苦。
  • MSVC 和 GCC 都能够使用常规的 #include 保护跳过重复包含,因此几乎不需要非标准的编译指示。
  • 我最近也喜欢上了#pragma 一次。我有点担心可移植性,但它确实使标题看起来更干净,顶部有一条短线,而不是多线复杂的包含保护。
【解决方案2】:

大多数编译器都对预编译的头文件提供某种支持。

删除完全不需要的包含的工具可能会很好。您似乎暗示您希望看到一个删除 needed 包含的内容,这些包含被其他包含冗余。我不会是它的忠实粉丝。有一天,有人可能会删除另一个多余的包含,然后其他一些可怜的 slob 将不得不追踪硬盘上某处的哪些包含文件具有所有那些突然出现在其上的缺失符号,这些符号突然出现在它们身上,没有明显的原因。

【讨论】:

  • 但是你提到的问题意味着包含文件的组织很差,不是吗?
  • 包含文件在定义上几乎没有组织。例如:如果我突然从 API 中缺少符号 foo 三个 #includes deep,我怎么知道 foo 恰好是在 Windows 发行版下的 include 子目录之一的 jobob.h 中定义的?除了搜索整个硬盘驱动器(或谷歌搜索“foo”并希望我幸运)之外,我不是。
【解决方案3】:

PC-Lint 将报告未使用的包含文件。

【讨论】:

    【解决方案4】:

    GamesFromWithin 博客有一篇关于 C++ 标头的好文章,甚至还有一个列出包含最多的标头的工具(因此是 pimpl/pch/forwarding 的主要候选者)。很好的工具,虽然是在 perl 中,而且当构建时间让我(太多)疯狂时,我实际上已经使用它几次来挖掘一些好的数据。

    【讨论】:

      【解决方案5】:

      在 GCC 中有一些关于 precompiled headers 主题的工作,但有一些限制(如果我记得的话,每次编译只能包含一个预编译头)。

      如果您不使用 GCC,此解决方案将无济于事,因为它不会生成可供其他编译器使用的简化头文件。

      【讨论】:

      • 我发现使用预编译头文件 gcc 又糟透了。我正在使用 Eiffel,它编译为 C 并生成一个非常大的头文件。它是 C 而不是 C++,使用预编译的编译比没有它的编译要慢。有人应该验证这是否也适用于 C++。使用 Sun Studio 编译器(我推荐它)或 Microsoft 的 VC 确实可以加速。
      【解决方案6】:

      Google 的cppclean 将帮助您删除不必要的头文件。它会产生一些误报,但它提供了一个很好的起点。请参阅我对类似问题和 cmets here 的回答。

      【讨论】:

        【解决方案7】:

        我从未听说过任何工具跟踪无用包含。而这只是微不足道的情况。

        但是,无论如何,您都应该注意它。如何组织标题和实现很大程度上取决于您如何组织代码。将您的项目视为独立的部分很重要,并且标题会部分强制您考虑它。

        【讨论】:

          【解决方案8】:

          我最近找到了一个工具,可以完全按照您的要求进行操作。

          在 Ben Ziegler 的博客 Double Buffered 上,他发布了一些 build system optimizations

          所以,我想出了复杂的 蛮力 Perl 脚本的解决方案。 基本上,我会扫描所有 我们代码中的 .c 文件并尝试 注释掉每个#include 指示。然后它会重新编译 代码库,看看是否有 错误。如果有它会恢复 注释掉,然后继续 下一个。

          结果并不理想。没有明显的构建时间改进。

          我仍然支持我的first answer;这是浪费时间,而且不明智。

          无论哪种方式都是他的perl script。你需要稍微修改一下才能让它在你的代码上工作,但核心应该是一样的。

          【讨论】:

          • 这不一定能胜任。仅仅因为您可以注释掉一个头文件并让系统仍然工作并不意味着您不应该包含它。如果您使用::std::string,您应该#include <string>,即使您包含的其他标题也包括#include <string>。充其量,这样的工具可以将您指向可能的无关标头。
          • 我完全同意。请参阅我的第一个答案。像这样的工具可以改变 c++ 代码的行为(c 代码可能是安全的,但仍然是个坏主意)。
          【解决方案9】:

          我曾经编写了一个非常简单的工具来执行此操作,并且效果非常好,尽管是以最可怕的蛮力方式。

          它在项目中的所有 h/cpp 文件中递归。
          对于每一个,它都会搜索#include,并在它们前面依次添加//,然后触发构建。如果构建返回错误,它会取消 //.如果构建成功,它会将包含行注释掉。

          处理所有 cpp 文件非常快(一个小时左右),但我必须让它整个周末都在代码库中运行以处理标题,但它实现起来非常简单且非常有效。而且每年只需运行一次左右,即可清除堆积的绒毛。

          它产生了微小但显着的差异(在 70 分钟的构建 IIRC 上大约需要 5 分钟)。

          【讨论】:

          • 请注意,这不一定会起作用 - 重载和模板解析可以成功编译,但在运行时会出错。
          • 我只是说像这样的自动化系统可能会产生微妙的、难以诊断的影响 - 确保其中涉及到人为因素。
          【解决方案10】:

          预编译的头文件很糟糕。

          这确实是一个链接器问题,除了获得更好的链接器(祝你好运)或以更容易使用链接器的方式编写代码之外,没有什么可做的。

          【讨论】:

          • 抱歉,链接器不处理头文件。这是一个编译器问题。问题是包含文件更改时。
          • 毫无意义的一揽子声明,结合虚假信息?您应该在您选择的平台上更多地研究编译/链接/运行过程。在某些平台(gcc/icc)上,预编译头文件可以非侵入性地添加到您的应用程序中,从而严格根据编译时性能选择是否使用它们。
          • 对不起,你显然不知道你在说什么。
          • Tom,当然它适用于 rinky dink 应用程序,但它只会在其他任何事情上引起头痛。在一百万个源文件中包含一百万个标题?除了最简单的情况外,这对任何事情都不起作用,而保护人们会导致这样的愚蠢问题,然后当有人说正确的话时会做出无知的回应。
          • Antiguru - SO 评论并不适合解释,所以我在带外发布了这个。请参阅tombarta.wordpress.com/2009/12/12/precompiled-headers-with-gcc 以获取非侵入式预编译标头的演示。
          猜你喜欢
          • 2011-05-06
          • 2023-03-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-03
          • 2014-11-29
          • 2016-10-13
          • 1970-01-01
          相关资源
          最近更新 更多