对于大多数大型项目来说,链接阶段所花费的时间可能是应用程序总体构建时间的重要部分。用户可以通过向链接器命令行添加’/time’标志来快速确定这一点。“Final:Total time”报告链接阶段花费的总时间。在查看链接时间时,基本上有两个主要场景需要考虑。第一个是开发人员迭代场景。在这个场景中,主要目标是使代码更改的周期尽可能快地在控制台上运行。开发人员通常愿意权衡可执行文件的大小和代码质量,以减少迭代时间。另一个需要考虑的场景是生成一个最终的构建,为发布做准备。在这种情况下,从大小和性能的角度来看,链接完成所需的时间仅次于确保生成最佳二进制文件。
这两种情况要求对链接器进行完全不同的配置。本博客描述了一组最佳实践,这些方法将使您在开发过程中迭代时同时获得VisualC++链接器的最大化,同时生成最终发布版本。我将在几个博客上讨论这个问题,其中一个博客将详细介绍开发人员迭代场景。
链接器和开发人员迭代
在开发人员迭代场景中,实现最佳链接器性能的关键是以增量方式链接应用程序。当以增量方式链接时,链接器直接更新在上一个链接上生成的二进制文件,而不是从头开始构建它们。这种方法要快得多,因为链接器只更新受代码更改影响的现有二进制文件的一部分,而不必从头开始从组成对象和库中重新创建二进制文件。除了增量更新二进制文件外,链接器还增量更新相应的PDB。
为了能够在后续链接中将代码添加到现有二进制文件中,链接器会在生成二进制文件时向其中插入额外的填充。因此,启用增量链接的二进制生成将比不启用增量链接的二进制生成大。在开发人员迭代场景中,额外的大小通常被认为是更快的链接时间的公平折衷。但是,较大的二进制文件在远程主机上部署的时间较长,因此您需要验证在特定场景中是否可以接受这种权衡。
即使链接器被正确配置为增量链接,不幸的是,今天有几个因素将迫使链接器后退并执行完整链接(我们正在努力改进这一点)。本节的其余部分描述了用于启用增量链接的一组开关,并提供了一组准则,以最大限度地提高增量链接成功的可能性。
推荐的链接器配置
通过传递 /增量 打开链接器命令行。如果您是在Visual Studio中构建, /增量 可以使用 启用增量链接 属性:
/对于使用visualstudio创建的项目,默认情况下,“增量”在调试配置中处于启用状态。默认情况下,对于版本和配置文件配置,/INCREMENTAL开关处于关闭状态。还要注意的是 /增量 如果您指定了 /调试 .
有两个开关可用于获取有关增量链接过程的诊断信息。这个 /verbose:incr 开关将打印各种诊断消息,可用于确定链接器何时必须放弃增量链接并退回到完整链接。例如,导致链接器返回到完整链接的条件之一是修改被链接的二进制文件所依赖的库(请参阅) 链接.libs 下面)。如果 /verbose:incr 如果已打开,并且库已更改,则将显示以下消息:
链接:库已更改;执行完全链接
如果成功执行增量链接, /verbose:incr 不产生输出。
我前面提到的另一个诊断开关是 /时间 . 除此之外, /时间 显示有关链接的每个阶段的信息。如果你看到这样的短语 IncrPass公司 在链接输出中 /时间 则标题已增量链接。输出中缺少这样的短语意味着链接器执行了完整的链接。下面是 /时间 在增量链接上:
链接器: 增量2 :间隔#1,时间=0.04710s[C:empIncrLinkDurangoDebugIncrLink.exe] 链接器:Wait PDB close Total time=0.02389s PB:9494528[C:empIncrLinkDurangoDebugIncrLink.exe] 链接器: 增量2 :间隔#2,时间=0.11271s[C:empIncrLinkDurangoDebugIncrLink.exe] 链接器:最终总时间=0.15984s<632942532369–632942948644>PB:5312512[C:empIncrLinkDurangoDebugIncrLink.exe]
总而言之,增量链接时建议使用的3个链接器开关是:
- /增量
- /verbose:incr
- /时间
另外值得注意的是,在某些情况下,您可以消除 /调试 选项,使链接器生成PDB文件。链接器生成.pdb文件所花费的时间已经被证明是整个链接时间的重要部分。如果有不使用此调试信息的场景,则 /调试 链接器标志将通过跳过pdb生成来减少链接时间。
调整链接器以获得最高的增量成功率
即使定义了所有推荐的开关,仍有几个因素可能导致链接器执行完整链接而不是增量链接。本节介绍这些因素以及如何防止它们发生。
工具集的选择很重要
Visual C++具有32位链接器和64位链接器。如果可能的话,应该使用64位链接器。增量链接在64位链接器中更可能成功,主要是因为增加了地址空间。较大的地址空间之所以重要,有两个原因。首先,64位链接器可以将比32位链接器更多的对象和库映射到内存中(地址空间不足是32位链接器增量链接更经常失败的原因之一)。
增加的地址空间对于增量链接很重要的第二个原因与链接器数据结构的加载有关。增量链接时,链接器会将一些内部数据结构保存到.ilk文件中。在随后的链接中,链接器尝试将该文件的内容加载到与上次运行相同的内存位置。如果无法在同一位置加载文件,则增量链接将失败。64位地址空间使链接器更有可能在所需地址加载.ilk的内容。
要验证是否正在使用64位链接器,请添加 /英属维尔京群岛 到编译器(而不是链接器)命令行。生成输出中的以下行确认正在使用64位链接器:
C:程序文件(x86)Microsoft Visual Studio 11.0VCBIN amd64型 链接程序 :版本11.00.65501.17015
请注意,上一行中的版本号可能会在VisualStudio的不同版本之间更改。
与链接器优化共存
链接器提供各种开关,以便在链接时启用优化。使用这些开关中的任何一个都将禁用增量链接。具体来说,避免使用 /opt:ref , /opt:icf , /订单 ,和 /LTCG公司 (链接时代码生成)在开发人员迭代场景中。如果在/INCREMENTAL处于启用状态时使用其中一个开关,则在构建时将看到如下输出:
链接:警告LNK4075:由于“/OPT:REF”规范而忽略“/INCREMENTAL”
这个 /opt:icf and /opt:ref linker 执行优化以删除相同和未引用的COMDAT。如果编译器能够证明数据或函数永远不会被引用,那么编译器只能优化掉数据或函数。除非 /如果启用了LTCG,编译器的可见性仅限于单个模块(.obj),因此对于具有全局作用域的数据和函数,编译器永远不会知道其他模块是否将使用它们。因此,编译器永远无法优化它们。
相比之下,链接器可以很好地查看将链接在一起的所有模块,因此它可以很好地优化掉未使用的全局数据和未引用的函数。但是,链接器在节级别处理二进制文件,因此如果未引用的数据和函数与节中的其他数据或函数混合,链接器将无法提取和删除未引用的数据或函数。为了使链接器能够删除未使用的全局数据和函数,每个全局数据成员或函数都放在一个单独的部分中。这些部分称为COMDATs。这些优化要求链接器跨所有输入模块收集和分析引用信息,这使得这些优化在增量链接时变得不切实际。
这个 /订单 开关可用于指定布置某些通信的顺序。指定此开关时,二进制所需的潜在更改量会导致禁用增量链接。
链路时间代码生成( /LTCG公司 )使链接器执行整个程序优化。一个常见的优化例子是 /LTCG公司 是跨模块的函数内联。与许多其他链接器优化一样,当/LTCG打开时,增量链接被禁用,因为链接器必须分析多个输入文件之间的引用。关闭链接时代码生成需要同时更改链接器和编译器命令行。具体来说,/LTCG必须从链接器命令行中删除,/GL必须从编译器命令行中删除。
链接.libs
如果您的标题链接在库(.lib文件)中,链接器增量链接的能力将受到显著影响。就增量链接而言,使用库最重要的影响是,对任何库所做的任何更改都将导致链接器放弃增量链接并执行完全链接。
对库的更改禁用增量链接的原因与链接器如何解析给定二进制引用的符号有关。当.obj链接入时,.obj文件中的所有符号都会复制到链接器正在生成的二进制文件中。但是当一个.lib被链接进来时,只有来自库的二进制引用的符号被链接进来。
如果库发生更改,则以前从该库解析的符号现在可能来自另一个库。 此外,链接器总是尝试从引用符号的库开始解析符号。 因此,如果一个引用从一个lib移动到另一个lib,那么有可能其他几个引用也必须移动。当面对这么多可能已经改变的可能性时,链接器会放弃增量链接。
对库的更改也可能根本不会影响符号查找。虽然从技术上讲,链接器可以进行广泛的分析,以确定发生了什么变化以及影响是什么,但在尝试确定是否可以保留增量链接所花费的时间与刚刚开始使用完整链接之间存在一个折衷。
话虽如此,如果您经常对.libs执行更改,我们会提供一种在VisualStudio中增量链接的方法。这可以通过启用“使用库依赖项输入”来实现,如下图所示:
更改链接选项或添加/删除生成工件(.objs/libs)
更改传递给链接器的选项集将始终导致完全链接,即使新的开关集与增量链接完全兼容。同样地,更改链接在一起以形成二进制文件的对象集和库集将始终导致完全链接。如果你有/verbose:incr on,你会看到的 更改链接输入集时,会出现如下消息:
链接:添加对象文件;执行完全链接
保留以前的生成工件
链接器需要来自上一个构建的多个工件才能增量链接。特别是,您必须保留:
- 上一个链接产生的二进制文件
- 与该二进制文件对应的pdb文件
- 链接器的 .ilk文件 从上一个链接
这个 二进制和pdb 因为没有它们,链接器就无法增量更新。这个 伊尔克先生 文件是必需的,因为它包含链接器从上一个生成保存的状态。当以增量方式链接时,链接器会将其某些内部数据结构的副本写入.ilk文件。您将在生成输出中找到此文件。.ilk文件包含链接器必须具有访问权限才能执行下一个增量链接的状态。
当链接开始时,链接器将打开.ilk文件,并尝试在上一个链接中加载它的相同地址加载它。如果找不到.ilk文件, 或者如果不能在所需的地址加载,链接器将返回到完整链接。
这个 ‘/verbose:incr’ 切换可以帮助您检测由于找不到上一个生成的输出之一而完成完整链接的情况。例如,如果删除了.pdb,您将在生成输出中看到以下内容:
LINK : program database C: empabc.pdb missing; performing full link
总结
当我们在微软致力于提高链接器的性能时,下面是一些应该做的和不应该做的事情,使用这些应该能够获得更好的链接构建吞吐量。在后续的博客中,我将介绍一些技巧,这些技巧可用于提高构建实验室和生产发布场景的链接性能。所以请继续关注!最后,如果您想让我们在博客上介绍一些其他与链接器相关的场景,或者只是一些古玩,并且对链接器的性能有更多的问题,请随时联系我们 我 . 我会尽力回答他们。