嗨,我叫李绍。我是Visual C++小组中的高级软件设计工程师。在本博客中,我们的团队将回顾VisualStudio11(VS11)桌面应用程序构建吞吐量与VisualStudio2010SP1(VS10)的比较。Jim Hogg、Mark Hall、Bill Bailey、Mohamed Magdy Mohamed和Valentin Isac也对这个博客做出了贡献。你可以参考 博客 如果您对新的Metro风格的应用程序构建吞吐量感兴趣,请参阅Tim Wagner的文章。
构建吞吐量是C++开发人员最重要的生产力因素之一,因此,构建吞吐量测试是我们对VisualC++进行全面性能测试的重要组成部分。我们针对编译器和链接器进行了构建吞吐量测试,还针对MSBuild构建引擎进行了构建吞吐量测试。对于每个版本,我们都有性能测试来检查整个端到端构建性能,包括在编译器前端、后端和链接器中花费的时间。对于那些可能不知道的人来说,编译器前端是解析和分析源代码以构建程序的中间表示(IR)的阶段。编译器后端是以IR作为输入、执行优化并以对象文件的形式生成代码的阶段。链接器将这些对象文件作为输入,并将它们组合成一个可执行文件。在使用/GL(compileforlinktimecode generation,或LTCG)构建的情况下,代码生成和优化发生在链接器阶段,在该阶段可以分析整个可执行文件的IR。
VisualC++中的每一个新版本都包含大量的新技术和特性,它们以源代码库和头文件的形式提供给客户。对于给定的编译器,源代码越多,编译所需的时间就越长。幸运的是,多年来处理器的进步抵消了这一功能的增加。在最近的版本中,随着处理器速度开始达到顶峰,我们已经在多核特性上进行了投资,例如multi-Proc MSBuild和/MP编译器开关。在VS11中,我们在后端使用多线程。对于需要大量优化时间的构建,这已经获得了一些巨大的成功。例如,由于减少了后端时间,Microsoft的SQL构建速度提高了30%。
VS11功能的增加是我们有史以来最大的增量之一。当我们将所有这些新技术集成到产品中时,我们的性能测试告诉我们,与以前的版本相比,它是如何影响总体构建性能的。尽管正在编译的新源代码数量显著增加,但在大多数情况下,构建时间的增加保持在可接受的增量。
但是,根据我们的测试结果,如果您有一个应用程序使用标准模板库,您可能会注意到由于C++标准所赋予的功能的增加而导致的构建速度较慢。在这个博客中,我们将分析一个具有代表性的应用程序的构建吞吐量,以展示我们所做的改进以及您可能遇到的可能的减速。
从“真实世界”应用程序生成吞吐量数据
我们的端到端吞吐量测试使用生产级别的“真实世界”应用程序。我们称之为RWC(真实世界代码)。下面是来自VS11的内部测试后版本的构建吞吐量数据,它位于我们的RWC项目中的一个项目上,跨越不同的机器配置。这个特定的桌面应用程序有大约50个C++项目,有280万行代码(LOC)。 该应用程序大量使用标准模板库。下面的“RWC”专门指这个应用程序。
配置 | 处理器类型 | 处理器编号 | 皇家音乐学院 | 操作系统(Windows 7 SP1) |
姓名 | (GB) | |||
规格A | 英特尔酷睿I7 | 4芯(8线) | 16 | x64个 |
(E31240) | ||||
规范B | Intel Core 2四元 | 4芯(4线) | 8 | x64个 |
(Q9550) | ||||
规格C | 英特尔奔腾 | 2芯(2线) | 三 | x86个 |
(G6950) |
图1 :RWC生成吞吐量(MSBuild 2 Proc(/m:2)和完全优化(/Ox))
图2 :RWC Build throughput with link/LTCG(LTCG:link-Time代码生成,MSBuild 2 Proc(/m:2),Compile with/GL,link with/LTCG)
图3 :RWC编译器后端吞吐量(MSBuild 2 Proc(/m:2))
图4 :RWC链路时间吞吐量(MSBuild 2 Proc(/m:2))
“完整构建时间”是从构建开始到构建完成的时间。它是构建完成所需的时钟时间。编译器前端(FE)、编译器后端(BE)和链接时间是跨所有进程在C1.dll和C1xx.dll(FE)、C2.dll(BE)和链接器中花费的总时间(累计时间)。由于这是一个多进程构建,并且有些项目为编译器启用了/MP,因此通常有多个编译器实例在运行,这就是为什么在工具中花费的总时间远远大于“完整构建时间”的原因。
在非LTCG构建(图1)中,与其他组件相比,链接器所做的工作最少,因此从图中很难看到链接器时间。同样,在LTCG构建中(图2),所有代码生成都发生在链接之后。由于链接器在后端运行之前消除了对象文件中所有冗余的模板实例化,因此在这种情况下,后端时间大大减少。
根据我们在这个特定应用程序上运行multi-proc MSBuild(MSBuild/M:n)时的数据,使用4-8 proc构建可以在总体构建时间上比2 proc稍微提高吞吐量。但是,由于资源争用和实际发生的并行构建的项目引用限制,2进程构建在总体吞吐量方面代表了此应用程序的多进程构建特性。因此,我们在这里使用2proc数据来表示构建吞吐量。
以下是一些基于数据的关键观察结果:
更快的编译器后端阶段
在VS11中,我们引入了一个新特性来支持创建多个编译器后端线程以提高构建性能。编译器后端一次对单个函数执行优化和代码生成,允许它为多个函数并行生成代码。这可能是一个胜利,特别是对于启用优化的代码生成,因为执行优化需要后端在每个函数中花费更多的时间。
在图1和图3中可以看到这一吞吐量优势。虽然这些都是编译器后端吞吐量方面令人印象深刻的改进,但我们注意到在这个场景中,后端时间只占总构建时间的一小部分。我们在这里强调它只是为了说明的目的。
较慢的编译器前端阶段
我们可以看到,在这个实际示例中,与VS10相比,VS11前端的性能下降了15%–20%(图1和图2)。我们的调查显示,这种性能下降并不在编译器本身,而是前端处理的模板实例化数量增加的结果。添加到STL的新功能显著增加了给定编译中模板实例化的数量。功能的主要增加是由新的C++标准规定的,并且也被我们的客户广泛要求。模板类型的增加迫使前端为给定的输入文件实例化更多的模板,这会导致总体构建性能下降。当然,我们还没有发布VS11,所以我们继续分析这个问题并寻找提高吞吐量的方法。
如前所述,链接器获取编译器生成的中间文件并生成最终程序集。由于编译器生成了更大的中间文件,您还可能会看到未优化构建和优化构建(LTCG)的链接时间都略有减慢,特别是在低端机器(如我们介绍的specc)上,因为要处理和合并的符号更多。
更快的LTCG构建
在一台高端机器上(如我们介绍的Spec a和Spec B),我们的测试显示LTCG构建中的链接时间更快(图4)。
对于许多版本,编译器支持链接时代码生成(LTCG),代码生成延迟到链接时,以便整个映像(EXE、DLL)的所有模块的信息都可用于优化。因此,代码生成可以在整个映像的上下文中完成,跨模块内联,应用自定义调用约定等。非LTCG构建通常将优化限制在单个对象文件的上下文中。
使用VS11,这种链接时代码生成可以在多个线程中完成,一次编译多个函数,并根据函数调用树确定的依赖关系进行警告。对于编译成更少、更大图像的项目来说,这是一个特别好的胜利。我们的数据显示,在构建“big-EXE”Microsoft产品SQL Server时,由于我们为提高LTCG构建吞吐量所做的工作,构建时间提高了约30%。请注意,SQLServer源代码不使用模板代码,这就是为什么它没有新的STL模板导致的生成吞吐量降低。
如何提高应用程序的构建性能?
由于生成吞吐量对C++开发人员的生产力非常重要,这里有一些建议可以帮助您提高生成吞吐量。
利用计算机上的CPU核心
从图1和图2可以看出,虽然总的FE时间已经回归,但是对于特定的应用程序,尤其是高端机器(Spec A和Spec B),总构建时间的回归仍然非常小。这是由于多进程构建的影响。在IDE中构建时,默认情况下,构建系统将使用计算机上的内核总数。您还可以通过进入“工具”->“选项”->“项目和解决方案”->“构建和运行”来修改多过程构建,更改“并行项目构建的最大数量”的设置。如果是在命令行上构建,请传入/m:n(n是要使用的进程数)。在命令行上使用MSBuild生成时的默认值为1。如前所述,尽管使用4个或更多过程生成可能会提供更好的性能,但对于此特定应用程序,将MSBuild设置为2 proc build(/m:2)会提供接近4或8 proc build的性能。此外,还可以为每个项目或每个文件设置/MP,以利用编译器级别的多进程构建。有关如何进一步优化构建的其他建议,您可以查看以下内容 博客 .
使用预编译头(PCH)
C++库头代表了一个活的、不断发展的库集合,它们的大小从发布到发布都有增加。例如,在VS11中,windows头的原始大小增加了13%。这是因为添加了更多与Windows 8相关的API函数和类型。新的Windows头文件将在创建PCH时增加更多的编译时间,而不是在使用PCH时。一旦构建,就会有更多的编译器 使用 PCH,其构建时间受到的影响很小,如果有的话。正确使用预编译头仍然是减少总体构建时间的最有效方法。如果应用程序广泛使用模板,还可以考虑在PCH文件中预实例化模板类型。
一个小的实验表明,对于一个包含200个文件的项目,使用PCH可以实现不同的效果。每个文件有大约一百行代码。PCH中包含各种库头。
图5: 最小化库头大小增加影响的PCH影响
使用托管增量生成
如果您有一个托管应用程序,则可以利用托管增量生成来避免在引用的程序集有不重要的更改时执行完整生成。有关更多信息,请阅读以下内容 博客 .
摘要
我们对编译器后端和链接器构建阶段进行了显著的吞吐量改进。如果您的应用程序不是STL的大量用户,并且花费大量时间进行优化,那么您应该看到构建时间的改进。
模板类型的增加将在使用大量模板的应用程序的总体构建吞吐量中起主导作用。由于C++ 11标准的更改,广泛使用STL的应用程序可能会经历较长的构建时间。由于VS11和windows8中的头文件总数增加,您还可能会遇到稍微长一点的构建时间。
为了提高总体构建时间,您可以利用机器上的多核来进行多过程构建。还要确保使用预编译头(PCH)。对于托管C++应用程序,请确保启用了管理的增量构建,以提高增量生成性能。
让我们知道!
如果您在将应用程序迁移到VS11时看到头文件数量的增长或STL模板的使用所带来的任何改进或减速超出了您的预期,那么我们有兴趣获取您的构建吞吐量性能数据。你可以回复这个博客,也可以通过微软网站给李绍发邮件。
除了捕获总体构建时间外,还可以通过向编译器传递/Bt和向链接器传递/time来获取每个编译器/链接器实例的编译器和链接器时间。
·在IDE中构建时,可以将/Bt设置为编译器的附加选项,将/Time设置为链接器的附加选项。确保您构建的应用程序“详细”详细。要设置详细信息,可以转到“工具”->“选项”->“项目和解决方案”->“构建和运行”,将“MSBuild项目构建详细信息”设置为“详细信息”。
·对于命令行生成,您可以在生成环境中设置u CL_u2;=/Bt和u LINK_2;=/Time,并使用MSBuild/v:d选项生成。
在构建日志中,您将看到编译器和链接器的每个实例在C1.dll(FE)、c1x.dll(FE)、C2.dll(BE)和链接器中花费的时间。您可能需要编写一个简单的脚本来将这些数字相加。请让我们知道,如果你想让我们张贴一个脚本,可以做的工作。或者,可以启用MSBuild“诊断”日志记录。它会给你在编译器任务和链接器任务中花费的时间,这与编译器和链接器的时间很接近。
我们希望这个博客能帮助您了解更多关于VS11中C++桌面应用程序构建吞吐量的信息。如果您对VS11的C++新类型应用程序生成吞吐量感兴趣,您可以查看一下 博客 以获得概述。请注意,我们继续改进C++ Metro风格的应用程序生成吞吐量。在即将发布的版本中,与Beta版相比,您应该会看到大约20%的总体构建吞吐量提高。
如果您有任何反馈,请告诉我们。我们感谢您的投入,以帮助我们提高构建性能。