【问题标题】:What techniques can be used to speed up C++ compilation times?可以使用哪些技术来加快 C++ 编译时间?
【发布时间】:2010-09-27 06:03:52
【问题描述】:

可以使用哪些技术来加快 C++ 编译时间?

这个问题出现在 Stack Overflow 问题的一些 cmets 中C++ programming style,我很想听听有什么想法。

我看过一个相关的问题,Why does C++ compilation take so long?,但这并没有提供很多解决方案。

【问题讨论】:

  • 您能给我们一些背景信息吗?还是您在寻找非常笼统的答案?
  • 非常类似于这个问题:stackoverflow.com/questions/364240/…
  • 一般答案。我有很多人编写的非常大的代码库。关于如何攻击的想法会很好。此外,为新编写的代码保持快速编译的建议会很有趣。
  • 请注意,构建时间的相关部分通常不是由编译器使用,而是由构建脚本使用
  • 我浏览了这个页面,没有看到任何关于测量的内容。我编写了一个小 shell 脚本,它为它接收到的每一行输入添加了一个时间戳,所以我可以在 'make' 调用中使用管道。这让我可以通过比较时间戳来查看哪些目标是最昂贵的、总编译或链接时间等。如果您尝试这种方法,请记住时间戳对于并行构建是不准确的。

标签: c++


【解决方案1】:

语言技巧

Pimpl 成语

看看 Pimpl idiom herehere,也称为 opaque pointer 或句柄类。它不仅可以加快编译速度,还可以在与non-throwing swap 函数结合使用时提高异常安全性。 Pimpl 习惯用法可让您减少标头之间的依赖关系并减少需要完成的重新编译量。

转发声明

尽可能使用forward declarations。如果编译器只需要知道 SomeIdentifier 是一个结构或指针或其他什么,请不要包含整个定义,这会迫使编译器做比它需要的更多的工作。这可能会产生级联效应,使这种方式比他们需要的要慢。

I/O 流以减慢构建速度而闻名。如果您在头文件中需要它们,请尝试 #include <iosfwd> 而不是 <iostream> 和 #include 仅在实现文件中的 <iostream> 头。 <iosfwd> 标头仅包含前向声明。不幸的是,其他标准标头没有相应的声明标头。

在函数签名中更喜欢按引用传递而不是按值传递。这将消除在头文件中#include 相应类型定义的需要,您只需要前向声明类型。当然,更喜欢 const 引用而不是 non-const 引用以避免模糊的错误,但这是另一个问题的问题。

保护条件

使用保护条件来防止头文件被多次包含在一个翻译单元中。

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

通过同时使用 pragma 和 ifndef,您可以获得普通宏解决方案的可移植性,以及一些编译器在存在 pragma once 指令的情况下可以进行的编译速度优化。

减少相互依赖

通常,您的代码设计越模块化且相互依赖越少,您重新编译所有内容的频率就越低。您还可以最终减少编译器必须同时对任何单个块执行的工作量,因为它需要跟踪的事实更少。

编译器选项

预编译头文件

这些用于为许多翻译单元编译一次包含标题的公共部分。编译器将其编译一次,并保存其内部状态。然后可以快速加载该状态,以便在编译具有相同标题集的另一个文件时抢占先机。

请注意,您只在预编译的头文件中包含很少更改的内容,否则您最终可能会比必要更频繁地进行完全重建。这是STL 标头和其他库包含文件的好地方。

ccache 是另一个利用缓存技术加快处理速度的实用程序。

使用并行

许多编译器/IDE 支持使用多个内核/CPU 同时进行编译。在GNU Make(通常与GCC 一起使用)中,使用-j [N] 选项。在 Visual Studio 中,首选项下有一个选项,允许它并行构建多个项目。您还可以将/MP option 用于文件级并行,而不仅仅是项目级并行。

其他并行实用程序:

使用较低的优化级别

编译器尝试优化的越多,它就越难工作。

共享库

将修改频率较低的代码移动到库中可以减少编译时间。通过使用共享库(.so.dll),您还可以减少链接时间。

获得更快的计算机

更多的 RAM、更快的硬盘驱动器(包括 SSD)和更多的 CPU/内核都将对编译速度产生影响。

【讨论】:

  • 预编译的头文件并不完美。使用它们的一个副作用是你会得到比必要更多的文件(因为每个编译单元都使用相同的预编译头),这可能会比必要更频繁地强制完全重新编译。只是要记住的事情。
  • 在现代编译器中,#ifndef 与 #pragma once 一样快(只要包含保护位于文件顶部)。因此,就编译速度而言,#pragma once 没有任何好处
  • 即使您只有 VS 2005,而不是 2008,您也可以在编译选项中添加 /MP 开关以启用 .cpp 级别的并行构建。
  • SSD 在编写此答案时非常昂贵,但今天它们是编译 C++ 时的最佳选择。编译时访问很多小文件;。这需要 SSD 提供的大量 IOPS。
  • 在函数签名中更喜欢按引用传递而不是按值传递。这将消除在头文件中#include 相应类型定义的需要 这是错误,您不需要具有完整类型来声明按值传递的函数,您只需要完整的类型来实现使用那个函数,但是在大多数情况下(除非你只是转发呼叫)你还是需要那个定义。
【解决方案2】:

我从事 STAPL 项目,这是一个高度模板化的 C++ 库。有时,我们必须重新审视所有技术以减少编译时间。在这里,我总结了我们使用的技术。其中一些技术已在上面列出:

查找最耗时的部分

虽然符号长度和编译时间之间没有经过证实的相关性,但我们观察到较小的平均符号大小可以提高所有编译器的编译时间。因此,您的首要目标是找到代码中最大的符号。

方法 1 - 根据大小对符号进行排序

您可以使用nm 命令根据符号的大小列出符号:

nm --print-size --size-sort --radix=d YOUR_BINARY

在此命令中,--radix=d 可让您查看十进制数字的大小(默认为十六进制)。现在通过查看最大的符号,确定您是否可以破坏相应的类并尝试通过将非模板部分分解为基类或将类拆分为多个类来重新设计它。

方法 2 - 根据长度对符号进行排序

您可以运行常规的nm 命令并将其传送到您最喜欢的脚本(AWKPython 等),以根据符号的长度对符号进行排序。根据我们的经验,与方法 1 相比,此方法确定了使候选人更好的最大问题。

方法 3 - 使用 Templight

Templight 是一个基于Clang 的工具,用于分析模板实例化的时间和内存消耗,并执行交互式调试会话以了解模板实例化过程”。

您可以通过查看 LLVM 和 Clang (instructions) 并在其上应用 Templight 补丁来安装 Templight。 LLVM 和 Clang 的默认设置是调试和断言,这些会显着影响您的编译时间。似乎 Templight 两者都需要,所以你必须使用默认设置。安装 LLVM 和 Clang 的过程大约需要一个小时左右。

应用补丁后,您可以使用位于您在安装时指定的构建文件夹中的templight++ 来编译您的代码。

确保 templight++ 在您的 PATH 中。现在要编译,将以下开关添加到 Makefile 中的 CXXFLAGS 或命令行选项:

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

或者

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

编译完成后,您将在同一文件夹中生成.trace.memory.pbf 和.trace.pbf。要可视化这些跟踪,您可以使用Templight Tools,它将这些跟踪转换为其他格式。按照这些instructions 安装 templight-convert。我们通常使用 callgrind 输出。如果您的项目很小,您也可以使用 GraphViz 输出:

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

生成的 callgrind 文件可以使用kcachegrind 打开,您可以在其中跟踪最耗时/内存消耗最多的实例化。

减少模板实例化的数量

虽然没有减少模板实例化数量的确切解决方案,但有一些指南可以提供帮助:

使用多个模板参数重构类

例如,如果你有一个班级,

template <typename T, typename U>
struct foo { };

TU 都可以有 10 个不同的选项,您已将此类的可能模板实例化增加到 100 个。解决此问题的一种方法是将代码的公共部分抽象为不同的类.另一种方法是使用继承反转(反转类层次结构),但在使用此技术之前请确保您的设计目标没有受到影响。

将非模板代码重构为单个翻译单元

使用这种技术,您可以编译一次公共部分,然后将其与您的其他 TU(翻译单元)链接。

使用外部模板实例化(C++11 起)

如果您知道类的所有可能实例化,则可以使用此技术在不同的翻译单元中编译所有案例。

例如,在:

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

我们知道这个类可以有三种可能的实例:

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

将上述内容放在翻译单元中,并在头文件中的类定义下方使用 extern 关键字:

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

如果您使用一组通用的实例化编译不同的测试,这种技术可以节省您的时间。

注意:此时 MPICH2 忽略显式实例化,并始终在所有编译单元中编译实例化的类。

使用统一构建

统一构建背后的整个想法是将您使用的所有 .cc 文件包含在一个文件中,并且只编译该文件一次。使用这种方法,您可以避免重新实例化不同文件的公共部分,如果您的项目包含大量公共文件,您可能还会节省磁盘访问。

例如,假设您有三个文件foo1.ccfoo2.ccfoo3.cc,它们都包括来自STLtuple。您可以创建一个 foo-all.cc,如下所示:

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

您只编译此文件一次,并可能减少三个文件之间的常见实例化。通常很难预测改进是否会显着。但一个明显的事实是,您将在构建中失去并行性(您不能再同时编译这三个文件)。

此外,如果这些文件中的任何一个碰巧占用了大量内存,您实际上可能会在编译结束之前耗尽内存。在某些编译器上,例如GCC,这可能会导致您的编译器因内存不足而导致 ICE(内部编译器错误)。所以除非你知道所有的利弊,否则不要使用这种技术。

预编译头文件

预编译头文件 (PCH) 通过将头文件编译为编译器可识别的中间表示形式,可以为您节省大量编译时间。要生成预编译的头文件,您只需要使用常规编译命令编译头文件。例如,在 GCC 上:

$ g++ YOUR_HEADER.hpp

这将在同一文件夹中生成YOUR_HEADER.hpp.gch file.gch 是 GCC 中 PCH 文件的扩展名)。这意味着如果您在其他文件中包含YOUR_HEADER.hpp,编译器将使用您之前在同一文件夹中的YOUR_HEADER.hpp.gch 而不是YOUR_HEADER.hpp

这种技术有两个问题:

  1. 您必须确保被预编译的头文件是稳定的并且不会改变 (you can always change your makefile)
  2. 每个编译单元只能包含一个 PCH(在大多数编译器上)。这意味着如果要预编译多个头文件,则必须将它们包含在一个文件中(例如,all-my-headers.hpp)。但这意味着您必须在所有地方都包含新文件。幸运的是,GCC 有解决这个问题的方法。使用-include 并为其提供新的头文件。您可以使用此技术用逗号分隔不同的文件。

例如:

g++ foo.cc -include all-my-headers.hpp

使用未命名或匿名的命名空间

Unnamed namespaces(又名匿名命名空间)可以显着减少生成的二进制文件大小。未命名的命名空间使用内部链接,这意味着在这些命名空间中生成的符号对其他 TU(翻译或编译单元)将不可见。编译器通常为未命名的命名空间生成唯一名称。这意味着如果你有一个文件 foo.hpp:

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

而你恰好在两个 TU 中包含了这个文件(两个 .cc 文件并分别编译它们)。这两个 foo 模板实例将不相同。这违反了One Definition Rule (ODR)。出于同样的原因,不鼓励在头文件中使用未命名的命名空间。随意在您的.cc 文件中使用它们,以避免符号出现在您的二进制文件中。在某些情况下,更改 .cc 文件的所有内部细节会导致生成的二进制文件大小减少 10%。

更改可见性选项

在较新的编译器中,您可以选择符号在动态共享对象 (DSO) 中可见或不可见。理想情况下,更改可见性可以提高编译器性能、链接时间优化 (LTO) 和生成的二进制大小。如果您查看 GCC 中的 STL 头文件,您会发现它被广泛使用。要启用可见性选择,您需要更改每个函数、每个类、每个变量的代码,更重要的是每个编译器。

借助可见性,您可以从生成的共享对象中隐藏您认为它们是私有的符号。在 GCC 上,您可以通过将 default 或 hidden 传递给编译器的 -visibility 选项来控制符号的可见性。这在某种意义上类似于未命名的命名空间,但以一种更复杂和侵入性的方式。

如果您想指定每个案例的可见性,您必须将以下属性添加到您的函数、变量和类中:

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

GCC 中的默认可见性是 default (public),这意味着如果将上面的内容编译为共享库 (-shared) 方法,foo2 和类 foo3 在其他 TU 中将不可见 (@987654375 @ 和 foo4 将可见)。如果您使用-visibility=hidden 编译,则只有foo1 可见。甚至foo4 也会被隐藏。

您可以在GCC wiki 上阅读有关可见性的更多信息。

【讨论】:

    【解决方案3】:

    我会推荐这些来自“内部游戏,独立游戏设计和编程”的文章:

    当然,它们已经很老了 - 您必须使用最新版本(或您可用的版本)重新测试所有内容,以获得真实的结果。无论哪种方式,它都是很好的灵感来源。

    【讨论】:

      【解决方案4】:

      过去对我来说效果很好的一种技术:不要独立编译多个 C++ 源文件,而是生成一个包含所有其他文件的 C++ 文件,如下所示:

      // myproject_all.cpp
      // Automatically generated file - don't edit this by hand!
      #include "main.cpp"
      #include "mainwindow.cpp"
      #include "filterdialog.cpp"
      #include "database.cpp"
      

      当然,这意味着您必须重新编译所有包含的源代码,以防任何源发生更改,因此依赖关系树会变得更糟。但是,将多个源文件编译为一个翻译单元会更快(至少在我使用MSVC 和 GCC 的实验中)并生成更小的二进制文件。我还怀疑编译器被赋予了更多的优化潜力(因为它可以一次看到更多的代码)。

      这种技术在各种情况下都会失效;例如,如果两个或多个源文件声明了同名的全局函数,编译器将退出。不过,我在任何其他答案中都找不到这种技术,这就是我在这里提到它的原因。

      不管怎样,KDE Project 自 1999 年以来就使用这种完全相同的技术来构建优化的二进制文件(可能用于发布)。切换到构建配置脚本被称为--enable-final。出于考古兴趣,我挖出了宣布此功能的帖子:http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2

      【讨论】:

      • 我不确定这是否真的是一回事,但我想在 VC++ (msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx) 中打开“整个程序优化”对运行时性能的影响应该与你建议的相同.但是,编译时间在您的方法中肯定会更好!
      • @Frerich:您正在描述 OJ 的回答中提到的 Unity 构建。我还看到它们被称为批量构建和主构建。
      • 那么 UB 与 WPO/LT​​CG 相比如何?
      • 这可能仅对一次性编译有用,而不是在您在编辑、构建和测试之间循环的开发期间。在现代世界中,四个核心是常态,也许几年后核心数量会更多。如果编译器和链接器不能使用多个线程,那么文件列表可能会被拆分为并行编译的&lt;core-count&gt; + N 子列表,其中N 是一些合适的整数(取决于系统内存和机器的其他情况)使用)。
      【解决方案5】:

      我将只链接到我的另一个答案:How do YOU reduce compile time, and linking time for Visual C++ projects (native C++)?。我想补充的另一点,但经常导致问题的是使用预编译的头文件。但是,请仅将它们用于几乎不会更改的部分(如 GUI 工具包标题)。否则,它们花费的时间将超过最终为您节省的时间。

      另一个选项是,当您使用 GNU make 时,打开 -j&lt;N&gt; 选项:

        -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.
      

      我通常在3 有它,因为我这里有一个双核。然后它将为不同的翻译单元并行运行编译器,前提是它们之间没有依赖关系。链接不能并行完成,因为只有一个链接器进程将所有目标文件链接在一起。

      但是链接器本身可以线程化,这就是GNU gold ELF 链接器所做的。它是优化的线程化 C++ 代码,据说链接 ELF 目标文件的速度比旧的 ld 快一个数量级(实际上包含在 binutils 中)。

      【讨论】:

      • 好的,是的。抱歉,我搜索时没有出现这个问题。
      • 您不必感到抱歉。那是针对 Visual C++ 的。您的问题似乎适用于任何编译器。所以没关系:)
      【解决方案6】:

      关于这个主题有一整本书,标题为 Large-Scale C++ Software Design(由 John Lakos 撰写)。

      这本书早于模板,所以在那本书的内容中添加“使用模板也会使编译器变慢”。

      【讨论】:

      • 这本书经常在这类话题中被提及,但对我来说它的信息很少。它基本上声明尽可能使用前向声明并解耦依赖关系。除了使用 pimpl idiom 存在运行时缺陷之外,这有点说明了显而易见的问题。
      • @gast128 我认为它的重点是使用允许增量重新编译的编码习惯,即,如果您在某处更改一点源代码,则不必重新编译所有内容。跨度>
      【解决方案7】:

      这里有一些:

      • 通过启动多重编译作业来使用所有处理器内核(make -j2 就是一个很好的例子)。
      • 关闭或降低优化(例如,GCC 使用-O1-O2-O3 快得多)。
      • 使用precompiled headers

      【讨论】:

      • 仅供参考,我发现启动更多进程通常比启动内核更快。例如,在四核系统上,我通常使用 -j8,而不是 -j4。原因是当一个进程在 I/O 上被阻塞时,另一个进程可能正在编译。
      • @MrFooz:几年前我通过在 i7-2700k(4 核,8 线程,我设置了一个常数乘数)上编译 Linux 内核(来自 RAM 存储)对此进行了测试。我忘记了确切的最佳结果,但正如您所建议的那样,-j12-j18-j8 快得多。我想知道在内存带宽成为限制因素之前您可以拥有多少个内核......
      • @MarkKCowan 这取决于很多因素。不同的计算机具有非常不同的内存带宽。如今,使用高端处理器,需要多个内核才能使内存总线饱和。此外,还有 I/O 和 CPU 之间的平衡。有些代码很容易编译,有些代码可能很慢(例如,有很多模板)。我目前的经验法则是-j,实际核心数是 2 倍。
      【解决方案8】:

      一旦您应用了上述所有代码技巧(前向声明、将公共标头中的标头包含减少到最低限度、使用 Pimpl... 将大部分细节推送到实现文件中),语言方面将无法获得其他任何东西,考虑你的构建系统。如果您使用 Linux,请考虑使用distcc(分布式编译器)和ccache(缓存编译器)。

      第一个,distcc,在本地执行预处理器步骤,然后将输出发送到网络中第一个可用的编译器。它要求网络中所有配置的节点中的编译器和库版本相同。

      后者,ccache,是一个编译器缓存。它再次执行预处理器,然后检查内部数据库(保存在本地目录中)是否已经使用相同的编译器参数编译了该预处理器文件。如果是这样,它只会从编译器的第一次运行中弹出二进制文件和输出。

      两者可以同时使用,这样如果 ccache 没有本地副本,它可以通过网络将其发送到具有 distcc 的另一个节点,否则它可以直接注入解决方案而无需进一步处理。

      【讨论】:

      • 我不认为 distcc 在所有配置的节点上都需要相同的 library 版本。 distcc 只进行远程编译,而不是链接。它还通过网络发送预处理代码,因此远程系统上可用的标头无关紧要。
      【解决方案9】:

      当我从大学毕业时,我看到的第一个真正具有生产价值的 C++ 代码在它们之间定义了标头的地方有这些神秘的 #ifndef ... #endif 指令。我以一种非常幼稚的方式向编写代码的人询问了这些总体问题,并被介绍到了大规模编程的世界。

      回到正题,使用指令来防止重复的标头定义是我在减少编译时间方面学到的第一件事。

      【讨论】:

      • 旧但黄金。有时会忘记显而易见的事情。
      • '包括警卫'
      【解决方案10】:

      更多内存。

      有人在另一个答案中谈到了 RAM 驱动器。我用80286Turbo C++(显示年龄)做到了这一点,结果非常惊人。机器崩溃时数据丢失也是如此。

      【讨论】:

      • 在 DOS 中你不能有太多内存
      • 每次你犯一些内存错误机器都会崩溃
      【解决方案11】:

      使用

      #pragma once
      

      位于头文件的顶部,因此如果它们在翻译单元中被包含多次,则标题的文本只会被包含和解析一次。

      【讨论】:

      • 虽然得到广泛支持,但#pragma once 是非标准的。见en.wikipedia.org/wiki/Pragma_once
      • 现在,常规的包含守卫具有相同的效果。只要它们位于文件的顶部,编译器就完全能够将它们视为#pragma once
      【解决方案12】:

      尽可能使用前向声明。如果一个类声明只使用一个类型的指针或引用,你可以直接声明它并在实现文件中包含该类型的头文件。

      例如:

      // T.h
      class Class2; // Forward declaration
      
      class T {
      public:
          void doSomething(Class2 &c2);
      private:
          Class2 *m_Class2Ptr;
      };
      
      // T.cpp
      #include "Class2.h"
      void Class2::doSomething(Class2 &c2) {
          // Whatever you want here
      }
      

      如果你做得足够多,更少的包含意味着预处理器的工作量会少得多。

      【讨论】:

      • 只有当同一个标题包含在多个翻译单元中时,这不重要吗?如果只有一个翻译单元(使用模板时经常出现这种情况),那么这似乎没有影响。
      • 如果只有一个翻译单元,为什么还要把它放在标题中呢?将内容放在源文件中不是更有意义吗?标题的全部意义不在于它可能包含在多个源文件中吗?
      【解决方案13】:

      你可以使用Unity Builds

      ​​​​

      【讨论】:

      • @idbrii,链接已失效。 Here is a snapshot on archive.org
      • 请注意,在这种情况下,每个项目(最终二进制文件、静态/共享库)将由一个内核构建,如果您有一个更大的二进制文件作为瓶颈,统一构建的缺点必须考虑(对项目对象的任何更改都需要重新构建它)
      【解决方案14】:

      仅出于完整性考虑:构建可能会很慢,因为构建系统很愚蠢以及编译器需要很长时间才能完成工作。

      阅读 Recursive Make Considered Harmful (PDF),了解 Unix 环境中有关此主题的讨论。

      【讨论】:

        【解决方案15】:

        不是关于编译时间,而是关于构建时间:

        • 如果您在工作时必须重建相同的文件,请使用 ccache 在你的构建文件上

        • 使用 ninja-build 代替 make。我目前正在编译一个项目 大约 100 个源文件,所有内容都由 ccache 缓存。提出需求 5分钟,忍者不到1。

        您可以使用 -GNinja 从 cmake 生成您的 ninja 文件。

        【讨论】:

          【解决方案16】:
          • 升级你的电脑

            1. 获取四核(或双四核系统)
            2. 获取大量 RAM。
            3. 使用 RAM 驱动器可大幅减少文件 I/O 延迟。 (有些公司生产的 IDE 和 SATA RAM 驱动器就像硬盘一样)。
          • 那么你有所有其他的典型建议

            1. 如果可用,请使用预编译的标头。
            2. 减少项目各部分之间的耦合量。更改一个头文件通常不需要重新编译整个项目。

          【讨论】:

            【解决方案17】:

            我对@9​​87654321@ 有一个想法。事实证明,对于我的项目来说,它毕竟没有太大的不同。但是它们仍然很小。试试吧!我很想知道它有多大帮助。

            【讨论】:

            • 嗯。为什么有人对此投反对票?我明天去试试。
            • 我希望反对票是因为它从来没有产生太大的影响。如果您有足够的未使用 RAM,操作系统无论如何都会智能地将其用作磁盘缓存。
            • @MSalters - 多少才算“足够”?我知道这是理论上的,但出于某种原因,使用 RAMdrive 确实实际上会显着提升。去图...
            • 足以编译您的项目并仍然缓存输入和临时文件。显然,GB 中的边数将直接取决于您的项目大小。应该注意的是,在较旧的操作系统(尤其是 WinXP)上,文件缓存非常懒惰,导致 RAM 未被使用。
            • 如果文件已经在内存中,那么内存驱动器肯定会更快,而不是先做一大堆慢速 IO,然后它们在内存中? (对已更改的文件重复重复 - 将它们写回磁盘等)。
            【解决方案18】:

            动态链接 (.so) 比静态链接 (.a) 快得多。特别是当您的网络驱动器速度较慢时。这是因为您拥有 .a 文件中需要处理和写出的所有代码。此外,还需要将更大的可执行文件写入磁盘。

            【讨论】:

            • 动态链接会阻止多种链接时优化,因此在许多情况下输出可能会变慢
            【解决方案19】:

            你在哪里度过你的时间?你受CPU限制吗?内存绑定?磁盘绑定?你能用更多的核心吗?更多内存?你需要RAID吗?您只是想提高当前系统的效率吗?

            在gcc/g++下,你看过ccache吗?如果您经常使用make clean; make,它会很有帮助。

            【讨论】:

              【解决方案20】:

              从 Visual Studio 2017 开始,您可以使用一些编译器指标来了解需要什么时间。

              将这些参数添加到 C/C++ -> 项目属性窗口中的命令行(附加选项): /Bt+ /d2cgsummary /d1reportTime

              您可以了解更多信息in this post

              【讨论】:

                【解决方案21】:

                首先,我们必须了解 C++ 与其他语言的不同之处。

                有人说C++也有很多特性。但是,嘿,有些语言的功能更多,而且速度远没有那么慢。

                有人说文件的大小很重要。不,源代码行与编译时间无关。

                但是等等,怎么可能呢?更多的代码行应该意味着更长的编译时间,有什么魔力?

                诀窍是很多代码行隐藏在预处理器指令中。是的。仅仅一个#include 就会破坏你模块的编译性能。

                你看,C++ 没有模块系统。所有*.cpp 文件都是从头开始编译的。因此,拥有 1000 个 *.cpp 文件意味着将您的项目编译一千次。你有更多吗?太糟糕了。

                这就是 C++ 开发人员不愿将类拆分为多个文件的原因。所有这些标头都很难维护。

                那么除了使用预编译的头文件、将所有 cpp 文件合并为一个并保持头文件的数量最少之外,我们还能做些什么呢?

                C++20 为我们带来了对模块的初步支持!最终,您将能够忘记#include 以及头文件带来的可怕编译性能。触摸一个文件?只重新编译那个文件!需要编译一个新的结帐?在几秒钟而不是几分钟和几小时内编译。

                C++ 社区应该尽快迁移到 C++20。 C++ 编译器开发者应该更加关注这一点,C++ 开发者应该开始测试各种编译器的初步支持,并使用那些支持模块的编译器。这是 C++ 历史上最重要的时刻!

                【讨论】:

                  【解决方案22】:

                  更快的硬盘。

                  编译器将许多(并且可能是巨大的)文件写入磁盘。使用 SSD 而不是典型的硬盘,编译时间要短得多。

                  【讨论】:

                    【解决方案23】:

                    在 Linux(可能还有其他一些 *NIX)上,您可以通过不盯着输出并更改为 另一个 TTY 来真正加快编译速度。

                    这里是实验:printf slows down my program

                    【讨论】:

                      【解决方案24】:

                      网络共享会大大减慢您的构建速度,因为搜索延迟很高。对于像 Boost 这样的东西,它对我产生了巨大的影响,尽管我们的网络共享驱动器非常快。当我从网络共享切换到本地 SSD 时,编译玩具 Boost 程序的时间从大约 1 分钟缩短到 1 秒。

                      【讨论】:

                        【解决方案25】:

                        如果您有一个多核处理器,Visual Studio(2005 和更高版本)以及GCC 都支持多处理器编译。如果你有硬件,那肯定是可以启用的。

                        【讨论】:

                        • @Fellman,查看其他一些答案——使用 -j# 选项。
                        【解决方案26】:

                        虽然不是“技术”,但我无法弄清楚具有许多源文件的 Win32 项目如何比我的“Hello World”空项目编译得更快。因此,我希望这对像我这样的人有所帮助。

                        在 Visual Studio 中,增加编译时间的一个选项是增量链接 (/INCREMENTAL)。它与链接时代码生成 (/LTCG) 不兼容,因此请记住在发布构建时禁用增量链接。

                        【讨论】:

                        • 禁用链接时代码生成不是一个好建议,因为它会禁用许多优化。您只需在调试模式下启用/INCREMENTAL
                        【解决方案27】:

                        使用动态链接而不是静态链接让您的编译速度更快。

                        如果你使用 t Cmake,激活属性:

                        set(BUILD_SHARED_LIBS ON)
                        

                        Build Release,使用静态链接可以获得更多优化。

                        【讨论】:

                          猜你喜欢
                          • 2010-09-16
                          • 2011-04-04
                          • 1970-01-01
                          • 1970-01-01
                          • 2010-09-07
                          • 2017-01-16
                          • 2020-08-21
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多