演练:使用按配置文件优化
本演练说明如何将“按配置优化”(PGO) 用于各种方案:从在 Visual Studio 集成开发环境 (IDE) 中使用 PGO 生成菜单的简单实现,到需要自定义配置的更高级的方案。 本演练还讨论了一些高级技术,这些技术使您能够使用 PGO 命令行工具自定义收集和合并分析数据的过程。 有关概述信息,请参见 按配置文件优化。
使用 PGO 生成应用程序
此示例方案说明了如何使用 PGO 生成简单的应用程序。
在 IDE 中使用 PGO 生成
按配置优化可以应用于任何本机 Visual C++ 项目。 以下步骤说明了如何在 Win32 应用程序中使用 PGO。
创建 Win32 项目
在**“文件”菜单中选择“新建”,然后选择“项目”**。 出现“新建项目”对话框。
在**“项目类型”窗格中选择“Visual C++”节点,然后在“模板”窗格中选择“Win32 项目”**。
在**“名称”**框中输入 PGODemo。
单击**“确定”**按钮。 “Win32 应用程序向导”随即出现。
保留所有默认设置。 单击**“完成”**按钮。
使用 PGO 生成 PGODemo 项目
在标准工具栏中单击**“解决方案配置”组合框并选择“发布”**配置。
在解决方案资源管理器中右击 PGODemo 项目节点。 在项目上下文菜单中选择**“按配置优化”,然后选择“检测”**菜单项。
在解决方案资源管理器中右击 PGODemo 项目节点。 在项目上下文菜单中选择**“按配置优化”,然后选择“运行检测/优化后的应用程序”**以启动应用程序。
备注
通过选择“调试”菜单中的“开始”或“开始执行(不调试)”,也可以启动检测应用程序。
重复步骤 3,使用不同的方案测试检测应用程序。 当检测应用程序关闭时,项目输出目录中将创建一个 .PGC 文件,其中包含了此次运行的配置文件数据。
在解决方案资源管理器中右击 PGODemo 项目节点。 在项目上下文菜单中选择**“按配置优化”,然后选择“优化”**。
在命令行上使用 PGO 生成
假设您想使用 PGO 优化从源文件 S1,…,Sn 生成一个应用程序 myapp.exe。 第一步是确保使用 /GL 编译器选项编译源文件 S1,…,Sn。
您还需指定符合您需要的优化选项,让我们假定您选择了 /O2 选项。 需要指出的是,使用 PGO 生成应用程序时如果不指定任何优化选项(例如,使用 /Od), 将会激发警告,并且编译器会自动切换到 LTCG 生成模式。 从命令行,您的第一步如下:
使用 /GL 编译源文件
cl.exe /c /GL /O2 S1.cpp … Sn.cpp
link /ltcg:pgi S1.obj … Sn.obj /out:myapp.exe
步骤 2 将创建一个 PGO 已检测映像 myapp.exe 和一个 myapp.pgd,myapp.pgd 是配置文件数据库文件,编译器将使用它来生成最终的经过优化的 myapp.exe。 .pgd 文件的默认名是应用程序名,而默认路径是输出路径。
在上一步中,您可以使用链接器选项 /pgd 更改为您应用程序生成的 .pgd 文件的默认名称。 可以这样使用 /pgd:
使用 /LTCG:PGI 和 /PGD 创建检测映像
- link /ltcg:pgi /pgd:mypgd.pgd S1.obj … Sn.obj /out:myapp.exe
您现在可以使用您的分析方案来测试所检测的 myapp.exe 了。 每次运行方案之后,您都会找到一个创建的 .pgc 文件。
备注
如果您同时从同一个路径运行多个 myapp.exe 映像,只会为所有映像创建一个 .pgc 文件,且此文件只有在所有实例都关闭后才会写入磁盘。
备注
.pgc 文件的默认路径是可执行文件路径。您可以在运行分析方案前,通过设置环境变量 VCPROFILE_PATH 来更改此路径。例如,设置 VCPROFILE_PATH=<some path>,然后运行您的已检测映像 myapp.exe。
您现在可以生成经过 PGO 优化的 myapp.exe 了。
使用 /LTCG:PGO 和 /PGD 创建优化映像
- link /ltcg:pgo /pgd:mypgd.pgd S1.obj … Sn.obj /out:myapp.exe
备注
在步骤 1 之前,需要在测试方案过程中在配置文件数据库文件 (.PGD) 所在的同一文件夹中创建所有的 .PGC 文件
自定义配置文件数据合并
让我们假设您的应用程序有两个主要的用户方案。 第一个方案的重要程度(或被用户处理的频繁程度)是第二个方案的两倍。
在以下步骤中您将了解如何使用 Visual Studio 开发环境和命令行工具对合并到 .PGD 文件中的某些配置文件数据赋予更高的权重。
在 IDE 中合并配置文件数据
创建 Win32 项目
在**“文件”菜单中选择“新建”,然后选择“项目”。 此时将出现“新建项目”**对话框。
在**“项目类型”窗格中选择“Visual C++”节点,然后在“模板”窗格中选择“Win32 项目”**。
在**“名称”**框中输入 PGODemo。
单击**“确定”**按钮。 “Win32 应用程序向导”随即出现。
保留所有默认设置。 单击**“完成”**按钮。
在 IDE 中使用合并的配置文件数据生成 PGODemo 项目
在标准工具栏中单击“解决方案配置”组合框并选择**“发布”**配置。
在解决方案资源管理器中右击 PGODemo 项目。 在快捷菜单中单击“按配置优化”,再单击“检测”。
在解决方案资源管理器中右击 PGODemo 项目。 在快捷菜单中单击“按配置优化”,然后单击“运行检测应用程序”。 这将启动您的应用程序。 请注意,也可以按正常方式,使用“调试”菜单中的“开始”或“开始执行(不调试)”启动检测应用程序。
重复两次步骤 3,为每一种用户方案各执行一次。 此步骤将在输出目录中创建两个 .PGC 文件:PGODemo!1.pgc 和 PGODemo!2.pgc。
在解决方案资源管理器中右击 PGODemo 项目。 单击“属性”,将出现“PGODemo 属性页”对话框。
在**“配置属性”中单击“生成事件”,再单击“预链接事件”。 在“命令行”**中键入如下命令:
"$(VCInstallDir)bin\pgomgr.exe" -merge:2 $(OutDir)\$(ProjectName)!1.pgc $(OutDir)\$(ProjectName).pgd
备注
此步骤将使用 pgomgr.exe 在启动链接器前的一个单独步骤中合并 PGODemo!1.pgc 以生成最终优化应用程序。链接器将在下一步再次调用 pgomgr.exe 以合并 PGODemo!2.pgc 文件,但保留默认权重 1。
在解决方案资源管理器中右击 PGODemo 项目。 在快捷菜单中单击**“按配置优化”,然后单击“优化”**。
从命令行合并配置文件数据
您可以使用命令行工具执行自定义配置文件数据合并,如下所示。
从命令行生成合并的配置文件数据
cl.exe /c /GL /O2 S1.cpp … Sn.cpp
link /ltcg:pgi S1.obj … Sn.obj /out:myapp.exe
前两个步骤将创建检测 myapp.exe。
通过两种方案测试所检测的 myapp.exe 将创建两个不同的 .PGC 文件:myapp!1.pgc 和 myapp!2.pgc。
使用 pgomgr.exe 给第一个方案赋予更高的权重,如下所示:
Pgomgr –merge:2 myapp!1.pgc myapp.pgd
运行链接命令以创建最终优化应用程序,如下所示
link /ltcg:pgo /pgd:myapp.pgd /out:myapp.exe
这将合并 myapp!2.pgc 并使其具有默认权重 1。
PGO 更新
在前面两种情况中,我们在生成并测试了检测后应用程序之后,使用了 /ltcg:pgo 链接器选项生成最终的优化后应用程序。 使用 /ltcg:pgo 选项时,链接器将执行一些检查,以确保用于生成检测后应用程序的输入文件(对象文件、库等)没有发生更改。 也就是说,若要使用 /ltcg:pgo,所有传递给链接器的输入文件必须与 /ltcg:pgi 步骤中传递的文件相同。
这是因为,生成检测应用程序和 .PGD 文件后的任何意外更改都会严重影响 PGO 代码的生成和优化决策。
假设当您生成了检测应用程序、创建了配置文件数据并生成了 PGO 优化应用程序之后,发现忘记了更新应用程序的版本号,或者,您发现了一个无足轻重的 bug,它只需要做一些细微的修复,不会严重影响应用程序的流程。 对于这种情况,我们可以使用 /ltcg:pgu 选项。 使用此选项时,链接器不会执行使用 /ltcg:pgo 选项时所执行的检查。 在 PGI 生成与 PGU 生成之间,您可以编辑和重新编译文件,甚至可以将新文件添加到应用程序中。
在下面的步骤中,您将了解如何使用 Visual Studio 开发环境和命令行工具以使用 /ltcg:pgu 选项。
IDE 中的更新
在 IDE 中使用 /LTCG:PGU 生成 PGODemo
在标准工具栏中单击“解决方案配置”组合框,并选择**“发布”**配置。
在解决方案资源管理器中右击 PGODemo 项目。 从快捷菜单中单击**“按配置优化”,然后单击“检测”**。
在解决方案资源管理器中右击 PGODemo 项目。 从快捷菜单中单击**“按配置优化”,然后单击“运行检测/优化后的应用程序”。 这将启动您的应用程序。 请注意,也可以按正常方式,使用“调试”菜单中的“启动调试”或“开始执行(不调试)”**启动检测后的应用程序。
在此步骤中,您可以对任何源文件应用任何更改。 如上面提到的,严重影响应用程序行为的更改可导致严重的性能衰退。 一般情况下,您可以做非常细微的更改,例如小的 bug 修复或资源文件更改。
在解决方案资源管理器中右击 PGODemo 项目。 从快捷菜单中单击**“按配置优化”,然后单击“更新”**。 此操作将只重新编译已更改的文件,然后启动带 /ltcg:pgu 选项而非 /ltcg:pgo 选项的链接器,同时允许使用编辑后的文件
备注
对于每一个缺少配置文件数据的新功能,和每个所采用的编辑方法使测试步骤中收集到的配置文件数据无效的功能,您都会收到相应的警告。在标准工具栏中单击“解决方案配置”组合框并选择“发布”配置。
备注
在步骤 5 中,如果您在解决方案资源管理器中右击更改源文件并单击“编译”,将删除输出文件夹中所有与 PGO 相关的文件,因为当您从 PGO 生成转移到标准生成时,项目系统将执行清除生成的操作。
在命令行上更新
您可以使用命令行工具执行 PGO 更新,如下所示。
在命令行上使用 /LTCG:PGU 生成
cl.exe /c /GL /O2 S1.cpp … Sn.cpp
link /ltcg:pgi S1.obj … Sn.obj /out:myapp.exe
前两个步骤将创建检测 myapp.exe。
测试检测 myapp.exe
link /ltcg:pgo /pgd:myapp.pgd /out:myapp.exe
步骤 4 将创建优化的 myapp.exe。 假设您在 Sm.cpp 中发现了一个小 Bug,则可以应用修复然后仅编译 Sm.cpp,如下所示。
cl /c /GL /O2 Sm.cpp
然后使用 /ltcg:pgu,根据在步骤 3 中创建的旧配置文件数据生成优化的应用程序。
link /ltcg:pgu /pgd:myapp.pgd /out:myapp.exe
自定义 PGO 配置
在此方案中,您将了解如何创建自定义 PGO 配置以生成包含多个项目的解决方案。 在此方案中,我们将在解决方案中添加一个 DLL 并将它链接到 PGODemo 应用程序。 我们将创建两个配置 PGIRelease 和 PGORelease,并使用它们生成整个解决方案,而不是使用“按配置优化”菜单项一次生成一个产品。
创建自定义 PGO 配置
在**“文件”菜单中选择“新建”,然后选择“项目”。 此时将出现“新建项目”**对话框。
在**“项目类型”窗格中选择“Visual C++”节点,然后在“模板”窗格中选择“Win32 项目”**。
在**“名称”**框中输入 PGODemo。
单击**“确定”按钮。 将出现“Win32 应用程序向导”**。
保留所有默认设置。 单击**“完成”**按钮。
现在您得到一个解决方案和一个名为 PGODemo 的项目。 下一步,创建 DLL 项目。
在解决方案资源管理器中右击解决方案,并选择**“添加”。 然后单击“新建项目”。 此时将出现“新建项目”**对话框。
在**“项目类型”窗格中选择“Visual C++”节点,然后在“模板”窗格中选择“Win32 项目”**。
在**“名称”**框中输入 PGODemoDLL。
单击**“确定”**按钮。 “Win32 应用程序向导”随即出现。
在**“应用程序设置”页中选择“DLL 应用程序”类型,然后单击“完成”**。
您现在将获得一个名为 PGODemoDLL 的 DLL 项目。
在解决方案资源管理器中双击 PGODemoDLL 项目,双击 PGODemoDLL.cpp 并添加如下代码:
__declspec(dllexport) void HelloMessage(void) { MessageBox(NULL, L"hello", L"PGODemoDLL", MB_OK); }
在解决方案资源管理器中双击 PGODemo 项目,双击 PGODemo.cpp 并在 _tWinMain 函数的定义前添加如下声明:
__declspec(dllimport) void HelloMessage(void);
在 _tWinMain 中主消息循环之前添加以下代码:
HelloMessage();
在解决方案资源管理器中,右击 PDODemo 解决方案并单击**“项目依赖项”。 出现“项目依赖项”**对话框。
在“依赖项”页上,单击在**“项目”组合框中选择的 PGODemo。 在“取决于”**列表中选中 PGODemoDLL。
下一步,您将创建 PGIRelease 和 PGORelease 配置。
在解决方案资源管理器中右击解决方案,单击**“配置管理器”。 单击 PGODemo 项目的“配置”组合框并单击<新建...>**。 将显示“新建项目配置”对话框。
在“项目配置名称”编辑框中键入“PGIRelease”,并在**“从此处复制设置”组合框中选择“发布”。 确保选中“创建新的解决方案配置”**复选框。
对 PGODemoDLL 项目重复相同的步骤,将 PGIRelease 配置添加到此项目中。 这一次确保未选中**“创建新的解决方案配置”**复选框。
对这两个项目都重复步骤 16 以创建 PGORelease 配置。 确保还从**“发布”配置中复制了设置。 关闭“配置管理器”**对话框。
现在我们已创建了两个配置:PGIRelease 和 PGORelease。
在解决方案资源管理器中右击 PGODemo 项目并单击**“属性”。 在“配置”组合框中选择“PGIRelease”配置。 在“配置属性”中单击“常规”。 单击“全程序优化”组合框并选择“按配置优化 – 检测”选项。 单击“应用”**保存您的更改。
在**“配置”组合框中选择“PGORelease”配置。 在“配置属性”中单击“常规”。 单击“全程序优化”组合框并选择“按配置优化 – 优化”。 单击“应用”**保存您的更改。
对 PGODemoDLL 项目的 PGIRelease 和 PGORelease 配置重复步骤 18。
现在我们将把每个项目的 PGORelease 配置的中间目录更改成与 PGIRelease 配置的输出目录相同。
在解决方案资源管理器中右击 PGODemo 项目并单击**“属性”。 在“配置”组合框中选择“PGORelease”配置。 在“配置”属性中单击“常规”。 在中间目录中键入“$(SolutionDir)PGIRelease”。 单击“应用”**并关闭对话框。
对 PGODemoDLL 项目重复步骤 21。
下一步,我们将把 PGODemoDLL.dll 的路径添加到 PGODemo 应用程序的路径环境变量中。
在解决方案资源管理器中右击 PGODemo 项目并单击**“属性”。 在“配置”组合框中选择“PGIRelease”配置。 在“配置属性”中单击“调试”。 在“环境”中键入如下内容,然后单击“应用”**:
path=%path%;$(SolutionDir)PGODemoDLL\PGIRelease
在**“配置”组合框中选择“PGORelease”配置。 在“配置”属性中单击“调试”。 在“环境”**中键入如下内容:
path=%path%;$(SolutionDir)PGODemoDLL\PGORelease
然后单击**“应用”,再单击“确定”**关闭对话框。
在解决方案资源管理器中右击 PGODemo 项目并单击**“属性”。 在“配置”组合框中选择“PGORelease”配置。 在“配置属性”中单击“链接器”,然后单击“优化”。 在“按配置优化数据库”**编辑框中将“$(TargetDir)”替换为“$(IntDir)\”。
对 PGODemoDLL 项目重复此步骤。
在解决方案资源管理器中右击 PGODemoDLL 项目并单击**“属性”。 在“配置”组合框中选择“PGORelease”配置。 在“配置属性”中单击“链接器”,然后单击“高级”。 在“导入库”**编辑框中将“$(TargetDir)”替换为“$(IntDir)\”。
在标准工具栏中单击**“解决方案配置”组合框并选择“PGIRelease”配置。 在解决方案资源管理器中右击解决方案并单击“生成解决方案”**。
这将为两个项目生成检测位。
单击**“调试”菜单,并单击“启动调试”或“开始执行(不调试)”**。 这样将启动 PGODemo。 关闭应用程序后将创建两个 .PGC 文件,两个输出文件夹 PGODemo\PGIRelease 和 PGODemoDLL\PGIRelease 中各有一个。
在标准工具栏中单击**“解决方案配置”组合框,并选择“PGORelease”配置。 在解决方案资源管理器中右击解决方案并单击“生成解决方案”**。
现在两个项目的 PGORelease 配置文件夹中都有了 PGO 优化映像。
自定义配置文件数据集合
配置文件数据集合的默认行为是,保存从检测应用程序启动到结束这段时间内,描述检测应用程序行为的所有数据。 PGO 附带了一个称为 PGOSWEEP 的工具,此工具可以帮助您自定义配置文件数据集合。 您可以使用 PGOSWEEP 收集在配置文件方案运行期间的某个特殊时间范围内的配置文件数据。
在下面的步骤中,您将了解如何使用 Visual Studio 开发环境和命令行工具来控制配置文件数据集合。
创建 Win32 项目
在**“文件”菜单中选择“新建”,然后选择“项目”。 出现“新建项目”**对话框。
在**“项目类型”窗格中选择“Visual C++ 项目”节点,然后在“模板”窗格中选择“Win32 项目”**。
在“名称”框中输入 PGODemo。
单击**“确定”按钮。 将出现“Win32 应用程序向导”**。
保留所有默认设置。 单击**“完成”**按钮。
在 IDE 中生成检测 PGODemo 项目
在标准工具栏中单击**“解决方案配置”组合框并选择“发布”**配置。
在解决方案资源管理器中右击 PGODemo 项目。 在快捷菜单中单击**“按配置优化”,然后单击“检测”**。
在解决方案资源管理器中右击 PGODemo 项目。 从快捷菜单中单击**“按配置优化”,然后单击“运行检测/优化后的应用程序”。 这将启动您的应用程序。 请注意,也可以按正常方式,使用“调试”菜单中的“启动调试”或“开始执行(不调试)”**启动检测后的应用程序。
在**“工具”菜单中单击“外部工具”。 出现“外部工具”对话框。 单击“添加”。 在“标题”编辑框中键入“Pgosweep”。 单击“命令”编辑框旁边的“浏览”按钮,找到 PGOSWEEP 工具的路径。 PGOSWEEP 应该安装在 Visual Studio 所在的 ...\VC\bin 文件夹中。 选中“提示输入参数”复选框,然后单击“确定”关闭“外部工具”**对话框。
在**“工具”菜单上单击“Pgosweep”。 出现一个对话框。 在“参数”**编辑框中键入:
$(TargetPath) $(TargetDir)$(TargetName)_custom!1.pgc
单击**“确定”**。
在 PGODemo 主窗口中单击**“文件”,并单击“退出”**结束配置文件数据集合的运行。
现在,如果您查看 $(OutDir),将会发现两个 .PGC 文件。 PGODemo_custom!1.pgc 是由 PGOSWEEP 创建的,它包含从配置文件运行开始直到我们称为 PGOSWEEP 这段时间内的所有配置文件数据。 第二个文件具有默认命名方案 PGODemo!1.pgc,它包含从清除到我们关闭检测应用程序这段时间内的所有配置文件数据。
一定要注意的是,只有遵从 $(ProjectName)!n.PGC(n 是一个数字)命名标准的 .PGC 文件 才会在生成优化映像时自动合并。 要合并我们在上一步中创建的 PGODemo_custom!1.pgc,需要添加一个自定义合并步骤(请参见本演练前面描述的“自定义配置文件数据合并”方案)。
使用这些步骤,您可以控制配置文件数据集合,然后在运行时基于应用程序的最重要部分来优化您的应用程序。
从命令行收集配置文件数据
您可以使用命令行工具执行自定义配置文件数据集合,如下所示。
从命令行生成合并的配置文件数据
创建检测可执行文件:
cl.exe /c /GL /O2 S1.cpp Sn.cpp link /ltcg:pgi S1.obj Sn.obj /out:myapp.exe
运行检测 myapp.exe,然后在运行中的任何时间使用 pgosweep 工具收集配置文件数据:
Pgosweep myapp.exe mycustomdata.pgc
注意 不遵从标准格式的 PGC 文件必须使用 Pgomgr 工具手动合并。
myapp.exe 关闭后,将自动创建另一个 .PGC 文件 (myapp!1.pgc)。 如果您不想将它作为配置文件数据的一部分,在运行链接器以生成优化 myapp.exe 之前,请确保将此文件删除或移出链接器工作目录。
使用 Pgomgr 合并 mycustomdata.pgc:
Pgomgr –merge mycustomdata.pgc myapp.pgd
运行链接命令以创建最终优化应用程序:
link /ltcg:pgo /pgd:myapp
使用 PgoAutoSweep 自定义配置文件数据集合
在运行时的任何时间,您都可以从应用程序中调用 PgoAutoSweep 以保存和重置配置文件数据。 下面的示例显示了这是如何工作的
下面的示例将创建两个 .PGC 文件。 第一个文件包含的数据描述了在计数等于 3 之前的运行时行为,而第二个文件包含在此之后直到应用程序终止这段时间内收集的数据。
#include <stdio.h>
#include <windows.h>
#include <pgobootrun.h>
int count = 10;
int g = 0;
void func2(void)
{
printf("hello from func2 %d\n", count);
Sleep(2000);
}
void func1(void)
{
printf("hello from func1 %d\n", count);
Sleep(2000);
}
void main(void)
{
while (count--)
{
if(g)
func2();
else
func1();
if (count == 3)
{
PgoAutoSweep("func1");
g = 1;
}
}
PgoAutoSweep("func2");
}