用C++构建更快的PCH建议

创造一个 预编译头(PCH) 是改进构建时间的成熟策略。PCH在构建开始时只处理一次频繁包含的头,从而消除了重复解析该头的需要。预编译头的选择传统上被视为一个猜测游戏,但现在不是了!在本文中,我们将向您展示如何使用 vcperf分析工具 以及 C++构建洞察力SDK 要精确定位头文件,您应该为项目预编译。我们将带您完成为开放源码构建PCH的过程 鬼火 项目,生成时间提高了40%。

null

如何获取和使用vcperf

本文中的示例使用 vcperf公司 ,此工具允许您捕获生成的跟踪并在Windows性能分析器(WPA)中查看它。最新版本在VisualStudio2019中提供。

1.按照以下步骤获取和配置 vcperf公司 和水渍险:

  1. 下载并安装最新版本 Visual Studio 2019 .
  2. 通过下载和安装 最新Windows ADK .
  3. 复制 性能msvcbuildinsights.dll 从VisualStudio2019的MSVC安装目录到新安装的WPA目录的文件。这个文件是C++构建的见解WPA插件,它必须可以用于WPA正确显示C++构建洞察力事件。
    1. MSVC的安装目录通常是: C:Program Files (x86)Microsoft Visual Studio2019 { Edition } VCToolsMSVC{Version}inHostx64x64 .
    2. WPA的安装目录通常是: C:Program Files (x86)Windows Kits10Windows Performance Toolkit .
  4. 打开 性能核心.ini 文件,并为 性能msvcbuildinsights.dll 文件。这告诉WPA在启动时加载C++构建见解。

您还可以获得最新的 vcperf公司 通过克隆和构建 vcperf GitHub存储库 . 您可以将构建的副本与Visual Studio 2019结合使用!

2.按照以下步骤收集构建的跟踪:

  1. 打开高架门 用于VS 2019的x64本机工具命令提示符 .
  2. 获取您的版本的跟踪:
    1. 运行以下命令: vcperf /start MySessionName .
    2. 从任何地方构建您的C++项目,甚至从VisualStudio中构建 vcperf公司 收集系统范围内的事件)。
    3. 运行以下命令: vcperf /stop MySessionName outputFile.etl . 此命令将停止跟踪,分析所有事件,并保存 输出文件.etl 跟踪文件。
  3. 打开你刚刚在WPA中收集的跟踪。

在WPA中查看头解析信息

C++构建透视提供了一个称为WPA视图 文件夹 它允许您查看程序中所有头的聚合解析时间。在WPA中打开跟踪后,可以通过从 图形浏览器 窗格到 分析 窗口,如下所示。

dragging files view from the Graph Explorer pane to the Analysis window

此视图中最重要的列是名为 含时 计数 ,分别显示相应头的聚合解析时间和包含该头的次数。

案例研究:使用vcperf和WPA为Irrlicht 3D引擎创建PCH

在这个案例研究中,我们将展示如何使用 vcperf公司 和WPA为Irrlicht开源项目创建一个PCH,使其构建速度提高40%。

如果您想按照以下步骤操作:

  1. 克隆 Irrlicht存储库 来自GitHub。
  2. 签出以下提交: 97472da9c22ae4a .
  3. 打开高架门 用于VS 2019预览的x64本机工具命令提示符 命令提示符并转到克隆Irrlicht项目的位置。
  4. 键入以下命令: devenv /upgrade .sourceIrrlichtIrrlicht15.0.sln . 这将更新解决方案以使用最新的MSVC。
  5. 下载并安装 DirectX软件开发工具包 . 构建Irrlicht项目需要这个SDK。
    1. 为了避免错误,您可能需要在安装DirectX SDK之前从您的计算机卸载微软Visual C++ 2010 X86可重新分发和微软Visual C++ 2010 X64可重新分发组件。你可以从 添加和删除程序 Windows 10中的“设置”页。DirectX SDK安装程序将重新安装这些设置。
  6. 获取完整重建Irrlicht的跟踪。从存储库的根目录运行以下命令:
    1. vcperf /start Irrlicht . 此命令将启动跟踪的收集。
    2. msbuild /m /p:Platform=x64 /p:Configuration=Release .sourceIrrlichtIrrlicht15.0.sln /t:Rebuild /p:BuildInParallel=true . 此命令将重建Irrlicht项目。
    3. vcperf /stop Irrlicht irrlicht.etl . 此命令将保存内部版本的跟踪 irrlicht.etl文件 .
  7. 在WPA中打开跟踪。

我们打开门 生成资源管理器 文件夹 一个视图位于另一个视图的顶部,如下所示。这个 生成资源管理器 视图显示构建持续了大约57秒。这可以通过查看视图底部的时间轴(标记为A)看到。这个 文件夹 视图显示聚合解析时间最长的头是 窗口.h 辐照器.h (标记为B)。它们分别被解析了45次和217次。

Files view showing includes with the greatest duration

我们可以通过重新排列 文件夹 按视图分组 包含者 现场。此操作如下所示。

Using the settings to rearrange columns

创建PCH

我们先添加一个新的 pch.h公司 文件位于解决方案的根目录下。这个头包含了我们要预编译的文件,并且将包含在RiLIKHT解决方案中的所有C和C++文件。我们只添加 辐照器.h 编译C++时,因为它与C不兼容。

Precompiled header

PCH文件必须先编译才能使用。因为RiLHCH解决方案包含C和C++文件,所以我们需要创建2个版本的PCH。我们通过添加 pch-cpp.cpp文件 pch-c.c.公司 解决方案根目录下的文件。这些文件只包含 pch.h公司 我们在上一步中创建的标题。

Precompiled header include

我们修改 预编译头 的属性 pch-cpp.cpp文件 pch-c.c.公司 文件如下所示。这将告诉visualstudio创建我们的2pch文件。

Changing Precompiled Header Output File from #(IntDir)pch-cpp.pch to $(IntDir)pch-c.pch

我们修改 预编译头 Irrlicht项目的属性如下所示。这会告诉VisualStudio在编译解决方案时使用我们的C++ PCH。

Using $(IntDir)pch-cpp.pch

我们修改 预编译头 解决方案中所有C文件的属性如下。这告诉visualstudio在编译这些文件时使用PCH的C版本。

Using $(IntDir)pch-c.pch

为了使用我们的PCH,我们需要在所有的C和C++文件中包含PCH.H头。为简单起见,我们通过修改 高级 C/C++属性 对于Irrlicht项目使用 /FI 编译器选项。这种变化导致 pch.h公司 即使没有显式添加include指令,也会在解决方案中每个文件的开头包含。

pch.h as a Forced Include File

在创建PCH之后,需要应用一些代码修复程序才能正确构建项目:

  1. 为整个irlicht项目添加HAVEu BOOLEAN的预处理器定义。
  2. 在2个文件中取消定义远预处理器定义。

有关更改的完整列表,请参阅 我们在GitHub的叉子 .

评估最终结果

在创建PCH之后,我们收集一个新的 vcperf公司 通过遵循 案例研究:使用vcperf和WPA为开源项目创建PCH 部分 . 我们注意到构建时间从57秒变为35秒,大约提高了40%。我们还注意到 窗口.h 辐照器.h 不再出现在 文件夹 查看解析时间的主要贡献者。

Windows.h and irrAllocator.h no longer show up in the Files view as top contributors to parsing time

使用C++构建洞察力SDK获得PCH建议

大多数分析任务都是用 vcperf公司 WPA也可以使用C++构建洞察力SDK编程实现。作为本文的补充,我们准备了 顶部页眉 SDK示例。它打印出聚合解析时间最高的头文件,以及它们相对于总编译器前端时间的百分比权重。它还打印出每个标题所包含的翻译单元的总数。

让我们重复上一节中的Irrlicht案例研究,但这次使用 顶部页眉 取样看看有什么发现 . 如果您想继续,请使用以下步骤:

  1. 克隆 C++构建洞察力SDK示例GITHUB库 在你的机器上。
  2. 构建 示例.sln 解决方案,针对所需的体系结构(x86或x64),并使用所需的配置(调试或发布)。示例的可执行文件将放置在 out/{architecture}/{configuration}/TopHeaders 文件夹,从存储库的根目录开始。
  3. 按照 案例研究:使用vcperf和WPA为Irrlicht 3D引擎创建PCH 部分以收集Irrlicht解决方案重建的痕迹。使用 vcperf /stopnoanalyze Irrlicht irrlicht-raw.etl 命令而不是 /stop 停止跟踪时的命令。这将生成一个适合SDK使用的未处理跟踪文件。
  4. 通过 irrlicht-raw.etl文件 trace作为 顶部页眉 可执行文件。

如下图所示, 顶部页眉 正确识别两者 窗口.h 辐照器.h 作为解析时间的主要贡献者。我们可以看到,它们分别包含在45个和217个翻译单元中,正如我们在WPA中已经看到的那样。

图片[11]-用C++构建更快的PCH建议-yiteyi-C++库

重新运行 顶部页眉 在我们的固定代码基上显示 窗口.h 辐照器.h 标题不再是一个问题。我们看到其他几个标题也从列表中消失了。这些标题由引用 辐照器.h ,并由 辐照器.h .

图片[12]-用C++构建更快的PCH建议-yiteyi-C++库

了解示例代码

我们首先过滤所有停止活动事件,只保留前端文件和前端传递事件。我们要求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 .

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享