ZEVORN.blog

June 1, 2024

Intel® VTune™ Profiler 分析 C++ 程序的常见性能瓶颈( Windows 平台)

article8.3 min to read

用例和先决条件

本篇文章将展示,在 Windows 平台如何通过 Intel® VTune™ Profiler (下文简称 VTune)来识别和分析串行/并行应用程序中的性能瓶颈。使用 VTune 自带的 matrix 示例矩阵乘法应用程序作为分析和优化对象(备注:由于采用 Github 作为文章图床,部分图片加载速度稍慢)。

本文章要求安装多个英特尔软件工具,如果为了方便,可以直接下载 Intel® oneAPI 基本工具套件,详细链接如下:

PS: 本文章使用 Intel® oneAPI DPC++/C++ 编译器为分析和性能增益跟踪建立通用基线。根据您使用的编译器,结果和工作流程可能会有所不同。另外当前使用的 VTune 版本为 2021,使用其他版本可能与下文操作有细微差异。

基本流程简述

文章将按照以下步骤识别并修复示例 matrix 应用程序中最突出的性能问题:

  1. 建立应用程序性能基线
  2. 确定矩阵应用程序中的主要瓶颈
  3. 消除内存访问瓶颈
  4. 评估性能改进
  5. 解决矢量化问题
  6. 进行微架构分析
  7. 可视化性能提升

以上步骤的详细操作流程,将在下文,分章节讲述。

建立程序性能基线

运行性能快照分析

在 VTune 中分析应用程序的第一步是创建项目。项目是保存分析目标配置和数据收集结果的容器。VTune 提供了一个预配置的示例矩阵项目,以与预构建的矩阵示例应用程序配合使用。

首先打开预配置 matrix 的项目:

  1. 启动 VTune Profiler GUI。

a. 运行以下脚本以设置相应的环境变量:

install-dir\env\vars.bat

PS: 对于 VTune ,默认 install-dir 值为:[Program Files]\Intel\oneAPI\vtune\version

b. 在“开始”菜单中找到 VTune Profiler 图标,然后启动 VTune Profiler。

可能需要以管理员身份运行 VTune 才能使用某些分析类型。

  1. VTune 欢迎屏幕在产品启动后显示。

ample (matrix) 项目应已在项目导航器中打开。如果是这样,则无需进一步操作。如果 sample (matrix) 项目导航器中的项目不可用,则需要手动打开项目:

a. 单击 Menu 按钮,然后选择 Open->Project... 以打开现有项目。

b. 浏览到本地计算机上的项目, matrix 然后单击 Open

默认情况下,它位于以下目录中:

[Users]\<user>\Documents\VTune\Projects\sample (matrix)

VTune 在项目导航器中打开 matrix 项目。

要启动 matrix 示例应用程序的性能快照分析,请执行以下操作:

单击 Configure Analysis 按钮开始新的分析。默认分析是针对 Performance Snapshot 分析预先配置的,用于分析本地系统上的 matrix 应用程序。 继续单击 Start 按钮运行分析。如图所示:

image

手动运行一段时间后点击暂停退出按钮,VTune 将完成收集的结果,并打开 Performance Snapshot 分析的 Summary 视点。

分析性能快照结果

image

“性能快照结果摘要”选项卡显示以下内容:

在这种情况下,可观察到以下突出性能瓶颈的主要指标:

  1. 此应用程序的 Elapsed Time 非常高。
  2. Memory Bound 指标较高,表示访存问题。因此,性能快照将突出显示访存分析,作为潜在的起点,并指示此性能瓶颈最严重,并且对总运行时间的贡献最大。
  3. 对于现代超标量处理器,IPC(每个周期的指令数)指标值非常低,表明处理器在大部分时间都处于停滞状态。
  4. 性能快照分析突出显示了 Hotspots 是一个很好的起点。通常, Hotspots 是首次深入分析的良好候选者。它突出显示热点或对运行时间贡献最大的代码区域。

接下来,从热点分析开始,查看 matrix 应用程序中的哪个代码区域对性能问题的影响最大。

分析程序性能瓶颈

运行 Hotspots 并分析

image

要从“性能快照摘要”窗口运行“热点”分析,请执行以下操作:

  1. 单击“分析”树中的 Hotspots 图标,打开 Configure Analysis 窗口。
  2. WHERE 窗格中,选择 Local Host
  3. 如果使用的是预提供 sample (matrix) 的项目,则应已配置 WHAT 窗格,如果没有,请在 Application 文本框中提供应用程序的路径。
  4. HOW 窗格中,预选了 Hotspots 分析。对于收集模式,可以在 User-Mode SamplingHardware Event-Based Sampling 的采样之间进行选择。这些采样方法各不相同,但通常,最好尽可能使用基于硬件事件的采样,因为它以更低的开销提供更详细的信息。
  5. 单击 Start 按钮运行分析。

示例应用程序退出后,VTune 将最终确定结果并打开“摘要”视点。

image

此视点提供了多个指标。将鼠标悬停在问号图标上可获取每个指标的详细说明。

请注意,应用程序的总 CPU Time 大约等于 642 秒。它是应用程序中所有线程的 CPU 时间总和。总线程数为 9,因此应用程序是多线程的。

Summary 窗口的 Top Hotspots 部分提供有关最耗时的函数(热点函数)的数据,这些函数按执行所花费的 CPU 时间排序。对于示例应用程序,该 multiply1 函数大约需要 640 秒才能执行,但显示在列表顶部,显示为最热门的函数。

Summary 窗口下方的 Effective CPU Utilization Histogram 直方图表示可用逻辑处理器的“已用时间和使用率级别”(Elapsed Time and usage level),并提供应用程序执行期间使用的逻辑处理器数的图形外观。理想情况下,图表的最高条形应与目标利用率级别匹配。

若要获取代码的每个函数视图,请切换到 Bottom-up 选项卡。默认情况下,网格中的数据按函数分组。可以使用网格顶部的 Grouping 菜单更改分组级别。

image

该 multiply1 函数执行所需的时间最多,约为 640 秒,并且显示 CPU 利用率很差。

若要获取每个函数的详细 CPU 利用率信息,请使用 Bottom-up 窗格中的扩展按钮展开“按利用率划分的有效时间”列项。

image

双击 Bottom-up 网格上的函数 multiply1 以打开源代码窗口。

image

PS:如果分析对象是其他程序,需要保证该程序包含调试信息,并保持源代码路径与调试信息一致。

请注意,最耗时的行归因于在 multiply1 函数中执行矩阵乘法的循环。若要分析此循环与内存相关的行为,还运行 Memory Access 分析。

运行 Memory Access 并分析

image

要运行内存访问分析,请执行以下操作:

  1. 单击先前收集的 Performance Snapshot 结果中的 Memory Access 图标,或单击主工具栏中的 Configure Analysis 按钮。
  2. 如果单击 Memory Access 图标,则应预先选择“内存访问”分析。如果没有,请在 HOW 窗格中选择此分析类型。
  3. HOW 窗格中,禁用 Analyze OpenMP regions 选项,因为此应用程序不需要该选项。
  4. 单击 Start 按钮运行分析。

分析结果如下:

image

再次注意,应用程序受到内存访问的严格限制。系统不单独受 DRAM 带宽约束这一事实表明,应用程序受频繁但较小的内存请求的约束,而不是受饱和的物理 DRAM 带宽的约束。

切换到 Bottom-up 选项卡,查看 multiply1 该函数的确切指标。

image

该 multiply1 函数位于网格的顶部,具有最高的 CPU 时间和较高的内存绑定指标值。

请注意,LLC Miss Count 指标非常高。这表明应用程序使用对缓存不友好的内存访问模式,这会导致处理器经常错过 LLC 并从 DRAM 请求数据,这在延迟方面代价高昂。

解决此问题的一个好方法是应用循环交换技术,在这种情况下,该技术会更改矩阵的行和列在主循环中的寻址方式。这样,消除了低效的内存访问模式,使处理器能够更好地利用 LLC。

消除内存访问瓶颈

按照以下步骤使用 Intel® oneAPI DPC++/C++ 编译器在 Microsoft Visual Studio 中编辑和重新编译代码:

  1. 在计算机上找到矩阵示例应用程序文件夹。默认情况下,它被放置在:[Documents]\VTune\samples\matrix;
  2. 打开位于文件夹中的 matrix.sln ..\matrix\vc15 Visual Studio 解决方案;
  3. 请确保在生成应用程序时启用了发布配置和 x64 平台;
  4. 在“解决方案资源管理器”中,右键单击项目, matrix 然后选择 Properties
  5. Configuration Properties -> General 中,将 Platform Toolset 更改为 Intel C++ Compiler version
  6. C/C++ -> General 菜单中,确保 Debug Information Format 设置为 Program Database (/Zi)
  7. C/C++ -> Optimization 菜单中,确保 Optimization 选项设置为 Maximum Optimizations (Favor Size) (/O1)
  8. 在 ** C/C++ > Diagnostics [Intel C++]** 菜单中,将 Optimization Diagnostic Level 设置为 Level 2 (/Qopt-report:2)
  9. 在 multiply.h 头文件的第 36 行,更改以下行:
- #define MULTIPLY multiply1+ #define MULTIPLY multiply2

第 9 步,将程序更改为使用 multiply.c 源文件中的 multiply2 函数,该函数实现了解决内存访问问题的 loop interchange 技术。最后生成应用程序。

评估性能改进

若要查看使用 loop interchange 技术提供的改进,再次运行性能快照分析:

image

观察以下主要指标:

在这种情况下,代码未矢量化,因为 Intel® oneAPI DPC++/C++ 编译器在使用首选的二进制大小 (/O1) 进行编译时不执行矢量化。

若要通过 Visual Studio 启用编译器的自动矢量化,请按照下列步骤操作:

  1. 右键单击项目, matrix 然后选择 Properties
  2. C/C++ > Optimization 菜单中,将 Optimization 选项设置为 Maximum Optimization (Favor Speed) (/O2)
  3. 保存配置更改,生成应用程序。

解决矢量化问题

在启用了 /O2 级别的情况下重新编译应用程序后,再次运行性能快照分析,以分析矢量化效率:

image

观察以下主要指标:

  1. 总体 Vectorization 指标等于 99.9%,表示代码已矢量化。
  2. 但是,128-bit Packed FLOPs 指标旁边有危险信号。将鼠标悬停在红旗图标或指标值上以获取问题描述。

image

在这种情况下,VTune 指示浮点指令的很大一部分是在部分矢量负载下执行的。

由于分析是在基于能够使用 AVX2 指令集的 Intel 处理器的机器上执行的,因此所有指令都仅使用 128 位寄存器执行,这意味着根本没有使用 256 位宽的 AVX2 寄存器。因此,VTune 将 128 位向量寄存器的 100.0% 利用率标记为问题。

要了解实际使用的向量指令集,需要运行 HPC 性能表征分析:

image

要运行分析,请执行以下操作:

  1. 单击分析树中的 HPC Performance Characterization 图标;
  2. 禁用 Collect stacksAnalyze Memory bandwidthAnalyze OpenMP regions 选项,因为矢量化分析不需要这些选项;
  3. 单击 Start 按钮运行分析。

数据收集完成后,VTune 将打开 HPC 性能特性分析的默认摘要窗口:

image

将焦点放在 Summary 窗口的 Vectorization 部分。

请注意,该 multiply2 函数的主循环使用较旧的 SSE2 指令集进行矢量化,而编译和分析是在支持 AVX2 的处理器上执行的。因此,部分硬件资源仍未得到充分利用。

为了能够使用适合于平台的向量指令集,一种可能的方法是指示编译器使用与执行编译的处理器中可用的最佳向量扩展相同的向量扩展。

按照以下步骤使用 Visual Studio 启用适合平台的矢量化:

  1. Solution Explorer 窗格中,右键单击矩阵项目,然后选择 Properties
  2. 导航到 C/C++ > Code Generation [Intel C++] 菜单;
  3. Intel Processor-Specific Optimization 选项设置为 Same as the host processor performing the compilation (/QxHost) ,以指示编译器使用执行编译的处理器上可用的最佳指令集扩展;
  4. 保存修改,生成应用程序。

运行 Performance Snapshot 分析以确保应用程序已正确矢量化。

image

观察以下主要指标:

The for the application has slightly decreased.

  1. 应用程序的 Elapsed Time 略有减少。
  2. Vectorization 指标等于 99.9%,因此代码是完全矢量化的。
  3. 总共 100.0% 的 Packed DP FLOP 指令使用 256 位寄存器执行。因此,即使不运行 HPC 性能表征分析,结论也是 AVX2 向量扩展得到了充分利用。
  4. VTune 还突出显示了微架构使用情况指标,并提供使用微架构探索分析,来了解应用程序对微架构的利用率不足的确切情况。

进行微架构分析

虽然之前的优化为应用程序的总运行时间带来了巨大的好处,但仍有需要改进的地方。性能快照分析强调,微架构没有得到很好的利用。

运行 Microarchitecture Exploration 分析以识别改进机会。

image

要运行微架构探索分析,请执行以下操作:

  1. Performance Snapshot 分析树中,单击 Microarchitecture Exploration 分析图标;
  2. HOW 窗格中,启用所有额外选项;
  3. 单击 Start 按钮运行分析。

分析结果如下:

image

此视图显示以下内容:

在这种情况下,请遵守以下指标:

综合考虑这些因素,得出的结论是应用程序存在访存问题。但是,此问题在性质上与以前使用 loop interchange 技术解决的访存问题略有不同。

在引入 loop interchange 之前,应用程序主要受到缓存不友好的内存访问模式的约束,这导致了大量的 LLC(最后一级缓存)未命中。这反过来又导致了对DRAM的频繁请求。

通常,大多数开发人员在达到所需的性能目标时会停止进一步优化其应用程序。通过优化 matrix 应用程序获得的性能改进使应用程序墙时间从大约 90 秒减少到大约 2.5 秒。

如果要进一步试验,可以修改代码以实现缓存阻塞技术。缓存阻止是一种重新排列数据访问的方法,其方式是将数据块加载到缓存中,并在需要时重复使用,从而大大减少 DRAM 访问的数量。

To modify the code to use the cache blocking technique: 若要修改代码以使用缓存阻止技术,请执行以下操作:

在 multiply.h 头文件中,更改第 36 行:

- #define MULTIPLY multiply2+ #define MULTIPLY multiply4

保存更改并重新编译应用程序。

这将修改代码以使用 multiply.c 源文件中的 multiply4 函数,该函数实现 cache blocking 技术。

重新编译应用程序后,可以运行所选的分析以确定性能改进。

可视化性能提升

可以使用 VTune 的性能分析结果对比功能,以更好地了解性能变化。

虽然可以比较不同分析类型(如热点和性能快照)的结果,但仅显示同时适用于两种分析类型的指标。

要比较结果:

  1. 单击 Main Toolbar 中的 Compare Results 按钮;
  2. 选择要比较的结果。

image

VTune 计算指标之间的差异,并打开默认的 ** Summary** 窗口:

image

可以看到,对于 matrix 示例应用程序,运行时间减少了近 88 秒。

注意事项

点我查看