创造一个 预编译头(PCH) 是改进构建时间的成熟策略。PCH在构建开始时只处理一次频繁包含的头,从而消除了重复解析该头的需要。预编译头的选择传统上被视为一个猜测游戏,但现在不是了!在本文中,我们将向您展示如何使用 vcperf分析工具 以及 C++构建洞察力SDK 要精确定位头文件,您应该为项目预编译。我们将带您完成为开放源码构建PCH的过程 鬼火 项目,生成时间提高了40%。
如何获取和使用vcperf
本文中的示例使用 vcperf公司 ,此工具允许您捕获生成的跟踪并在Windows性能分析器(WPA)中查看它。最新版本在VisualStudio2019中提供。
1.按照以下步骤获取和配置 vcperf公司 和水渍险:
- 下载并安装最新版本 Visual Studio 2019 .
- 通过下载和安装 最新Windows ADK .
- 复制 性能msvcbuildinsights.dll 从VisualStudio2019的MSVC安装目录到新安装的WPA目录的文件。这个文件是C++构建的见解WPA插件,它必须可以用于WPA正确显示C++构建洞察力事件。
- MSVC的安装目录通常是:
C:Program Files (x86)Microsoft Visual Studio2019 { Edition } VCToolsMSVC{Version}inHostx64x64
. - WPA的安装目录通常是:
C:Program Files (x86)Windows Kits10Windows Performance Toolkit
.
- MSVC的安装目录通常是:
- 打开 性能核心.ini 文件,并为 性能msvcbuildinsights.dll 文件。这告诉WPA在启动时加载C++构建见解。
您还可以获得最新的 vcperf公司 通过克隆和构建 vcperf GitHub存储库 . 您可以将构建的副本与Visual Studio 2019结合使用!
2.按照以下步骤收集构建的跟踪:
- 打开高架门 用于VS 2019的x64本机工具命令提示符 .
- 获取您的版本的跟踪:
- 运行以下命令:
vcperf /start MySessionName
. - 从任何地方构建您的C++项目,甚至从VisualStudio中构建 vcperf公司 收集系统范围内的事件)。
- 运行以下命令:
vcperf /stop MySessionName outputFile.etl
. 此命令将停止跟踪,分析所有事件,并保存 输出文件.etl 跟踪文件。
- 运行以下命令:
- 打开你刚刚在WPA中收集的跟踪。
在WPA中查看头解析信息
C++构建透视提供了一个称为WPA视图 文件夹 它允许您查看程序中所有头的聚合解析时间。在WPA中打开跟踪后,可以通过从 图形浏览器 窗格到 分析 窗口,如下所示。
此视图中最重要的列是名为 含时 和 计数 ,分别显示相应头的聚合解析时间和包含该头的次数。
案例研究:使用vcperf和WPA为Irrlicht 3D引擎创建PCH
在这个案例研究中,我们将展示如何使用 vcperf公司 和WPA为Irrlicht开源项目创建一个PCH,使其构建速度提高40%。
如果您想按照以下步骤操作:
- 克隆 Irrlicht存储库 来自GitHub。
- 签出以下提交:
97472da9c22ae4a
. - 打开高架门 用于VS 2019预览的x64本机工具命令提示符 命令提示符并转到克隆Irrlicht项目的位置。
- 键入以下命令:
devenv /upgrade .sourceIrrlichtIrrlicht15.0.sln
. 这将更新解决方案以使用最新的MSVC。 - 下载并安装 DirectX软件开发工具包 . 构建Irrlicht项目需要这个SDK。
- 为了避免错误,您可能需要在安装DirectX SDK之前从您的计算机卸载微软Visual C++ 2010 X86可重新分发和微软Visual C++ 2010 X64可重新分发组件。你可以从 添加和删除程序 Windows 10中的“设置”页。DirectX SDK安装程序将重新安装这些设置。
- 获取完整重建Irrlicht的跟踪。从存储库的根目录运行以下命令:
-
vcperf /start Irrlicht
. 此命令将启动跟踪的收集。 -
msbuild /m /p:Platform=x64 /p:Configuration=Release .sourceIrrlichtIrrlicht15.0.sln /t:Rebuild /p:BuildInParallel=true
. 此命令将重建Irrlicht项目。 -
vcperf /stop Irrlicht irrlicht.etl
. 此命令将保存内部版本的跟踪 irrlicht.etl文件 .
-
- 在WPA中打开跟踪。
我们打开门 生成资源管理器 和 文件夹 一个视图位于另一个视图的顶部,如下所示。这个 生成资源管理器 视图显示构建持续了大约57秒。这可以通过查看视图底部的时间轴(标记为A)看到。这个 文件夹 视图显示聚合解析时间最长的头是 窗口.h 和 辐照器.h (标记为B)。它们分别被解析了45次和217次。
我们可以通过重新排列 文件夹 按视图分组 包含者 现场。此操作如下所示。
创建PCH
我们先添加一个新的 pch.h公司 文件位于解决方案的根目录下。这个头包含了我们要预编译的文件,并且将包含在RiLIKHT解决方案中的所有C和C++文件。我们只添加 辐照器.h 编译C++时,因为它与C不兼容。
PCH文件必须先编译才能使用。因为RiLHCH解决方案包含C和C++文件,所以我们需要创建2个版本的PCH。我们通过添加 pch-cpp.cpp文件 和 pch-c.c.公司 解决方案根目录下的文件。这些文件只包含 pch.h公司 我们在上一步中创建的标题。
我们修改 预编译头 的属性 pch-cpp.cpp文件 和 pch-c.c.公司 文件如下所示。这将告诉visualstudio创建我们的2pch文件。
我们修改 预编译头 Irrlicht项目的属性如下所示。这会告诉VisualStudio在编译解决方案时使用我们的C++ PCH。
我们修改 预编译头 解决方案中所有C文件的属性如下。这告诉visualstudio在编译这些文件时使用PCH的C版本。
为了使用我们的PCH,我们需要在所有的C和C++文件中包含PCH.H头。为简单起见,我们通过修改 高级 C/C++属性 对于Irrlicht项目使用 /FI
编译器选项。这种变化导致 pch.h公司 即使没有显式添加include指令,也会在解决方案中每个文件的开头包含。
在创建PCH之后,需要应用一些代码修复程序才能正确构建项目:
- 为整个irlicht项目添加HAVEu BOOLEAN的预处理器定义。
- 在2个文件中取消定义远预处理器定义。
有关更改的完整列表,请参阅 我们在GitHub的叉子 .
评估最终结果
在创建PCH之后,我们收集一个新的 vcperf公司 通过遵循 案例研究:使用vcperf和WPA为开源项目创建PCH 部分 . 我们注意到构建时间从57秒变为35秒,大约提高了40%。我们还注意到 窗口.h 和 辐照器.h 不再出现在 文件夹 查看解析时间的主要贡献者。
使用C++构建洞察力SDK获得PCH建议
大多数分析任务都是用 vcperf公司 WPA也可以使用C++构建洞察力SDK编程实现。作为本文的补充,我们准备了 顶部页眉 SDK示例。它打印出聚合解析时间最高的头文件,以及它们相对于总编译器前端时间的百分比权重。它还打印出每个标题所包含的翻译单元的总数。
让我们重复上一节中的Irrlicht案例研究,但这次使用 顶部页眉 取样看看有什么发现 . 如果您想继续,请使用以下步骤:
- 克隆 C++构建洞察力SDK示例GITHUB库 在你的机器上。
- 构建 示例.sln 解决方案,针对所需的体系结构(x86或x64),并使用所需的配置(调试或发布)。示例的可执行文件将放置在
out/{architecture}/{configuration}/TopHeaders
文件夹,从存储库的根目录开始。 - 按照 案例研究:使用vcperf和WPA为Irrlicht 3D引擎创建PCH 部分以收集Irrlicht解决方案重建的痕迹。使用
vcperf /stopnoanalyze Irrlicht irrlicht-raw.etl
命令而不是/stop
停止跟踪时的命令。这将生成一个适合SDK使用的未处理跟踪文件。 - 通过 irrlicht-raw.etl文件 trace作为 顶部页眉 可执行文件。
如下图所示, 顶部页眉 正确识别两者 窗口.h 和 辐照器.h 作为解析时间的主要贡献者。我们可以看到,它们分别包含在45个和217个翻译单元中,正如我们在WPA中已经看到的那样。
重新运行 顶部页眉 在我们的固定代码基上显示 窗口.h 和 辐照器.h 标题不再是一个问题。我们看到其他几个标题也从列表中消失了。这些标题由引用 辐照器.h ,并由 辐照器.h .
了解示例代码
我们首先过滤所有停止活动事件,只保留前端文件和前端传递事件。我们要求C++构建洞察力SDK在前端文件事件的情况下为我们解压缩事件堆栈。这是通过打电话来完成的 MatchEventStackInMemberFunction
,它将从堆栈中获取与 TopHeaders::OnStopFile
. 当我们有一个前端传递事件,我们只是保持总的前端时间直接跟踪。
AnalysisControl OnStopActivity(const EventStack& eventStack) override { switch (eventStack.Back().EventId()) { case EVENT_ID_FRONT_END_FILE: MatchEventStackInMemberFunction(eventStack, this, &TopHeaders::OnStopFile); break; case EVENT_ID_FRONT_END_PASS: // Keep track of the overall front-end aggregated duration. // We use this value when determining how significant is // a header's total parsing time when compared to the total // front-end time. frontEndAggregatedDuration_ += eventStack.Back().Duration(); break; default: break; } return AnalysisControl::CONTINUE; }
我们使用 OnStopFile
函数将所有头的解析时间聚合到 std::unordered_map fileInfo_
结构。我们还跟踪包含文件的翻译单元的总数,以及头的路径。
AnalysisControl OnStopFile(FrontEndPass fe, FrontEndFile file) { // Make the path lowercase for comparing std::string path = file.Path(); std::transform(path.begin(), path.end(), path.begin(), [](unsigned char c) { return std::tolower(c); }); auto result = fileInfo_.try_emplace(std::move(path), FileInfo{}); auto it = result.first; bool wasInserted = result.second; FileInfo& fi = it->second; fi.PassIds.insert(fe.EventInstanceId()); fi.TotalParsingTime += file.Duration(); if (result.second) { fi.Path = file.Path(); } return AnalysisControl::CONTINUE; }
在分析结束时,我们打印出为聚合解析时间最长的头收集的信息。
AnalysisControl OnEndAnalysis() override { using namespace std::chrono; auto topHeaders = GetTopHeaders(); if (headerCountToDump_ == 1) { std::cout << "Top header file:"; } else { std::cout << "Top " << headerCountToDump_ << " header files:"; } std::cout << std::endl << std::endl; for (auto& info : topHeaders) { double frontEndPercentage = static_cast<double>(info.TotalParsingTime.count()) / frontEndAggregatedDuration_.count() * 100.; std::cout << "Aggregated Parsing Duration: " << duration_cast<milliseconds>( info.TotalParsingTime).count() << " ms" << std::endl; std::cout << "Front-End Time Percentage: " << std::setprecision(2) << frontEndPercentage << "% " << std::endl; std::cout << "Inclusion Count: " << info.PassIds.size() << std::endl; std::cout << "Path: " << info.Path << std::endl << std::endl; } return AnalysisControl::CONTINUE; }
告诉我们你的想法!
我们希望本文中的信息有助于您理解如何使用C++构建见解创建新的预编译头,或者优化现有的预览头。
给予 vcperf公司 今天下载最新版本的 Visual Studio 2019 ,或直接从 vcperf Github存储库 . 试试这个 顶部页眉 通过克隆 C++构建洞察力示例库 从GitHub,或参考官方 C++构建洞察SDK文档 建立自己的分析工具。
您是否能够通过提供的头文件信息来改进构建时间 vcperf公司 还是C++构建洞察力SDK?让我们知道在下面的评论,在Twitter上 (@VisualC) ),或通过电子邮件 visualcpp@microsoft.com .