65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 uProf 在 AMD CPU 上进行 IBS 性能分析

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2018 年 10 月 27 日

CPOL

13分钟阅读

viewsIcon

14547

IBS(基于指令的采样)需要一个不同的视角来充分理解

引言

随着“Zen 核心”(Ryzen、Threadripper 和 EPYC)的发布,AMD CPU 的受欢迎程度日益增长。运行这些系统的程序员或 IT 管理员可能会想知道他们的代码性能如何。然而,像 VTune 这样的基于 Intel 的工具在 AMD CPU 上不起作用,因为它们依赖于 Intel 特定的功能(如 PEBS 或 LBR)来计数缓存命中或分支错误预测。

相反,程序员必须使用 AMD CPU 内置的工具,即 AMD PMCs 或 IBS,通过 AMD uProf 程序。

AMD“PMC”计数器是大多数程序员已经熟悉的老式硬件计数器。CPU 内部的计数器会累加特定事件,例如分支错误预测。每次分支错误预测,计数器就会增加。一旦计数器累加到用户可配置的值,性能分析器就会暂停程序,收集一些信息(当前指令指针、时间等),然后重新启动程序。这些信息稍后由性能分析器进行分析,程序员使用分析结果来优化他们的代码。

但是本文 **不** 讨论 PMC 计数器。许多文章已经涵盖了经典的性能计数器,它们广泛适用于现场的许多不同 CPU。本文将重点介绍 AMD 的“IBS”计数器,这是一种截然不同的方法。

总的来说,IBS 颠倒了性能计数器的方法,并对指令本身进行性能分析,从而产生一套相似的统计数据,但含义不同。总的来说,我建议程序员结合使用经典的性能计数器和 IBS 来理解性能。本文重点介绍鲜为人知的、AMD 特有的 IBS 计数器。

基于指令的采样背景

虽然大多数程序员都熟悉 AMD 的经典 PMC 计数器的工作方式,但它们的实现存在“偏差”问题。特别是,现代 CPU 是流水线式、超标量且乱序执行的,因此很难准确地确定现代“程序计数器”或“指令指针”在当今的含义。由于近百条指令同时在流水线、执行单元或退休队列中执行,因此在性能分析器写入性能分析信息时,程序计数器是不明确的。程序员会注意到在加法或乘法指令上的分支错误预测,以及其他类似的无意义现象。

应该注意的是,Intel CPU 通过 PEBS 解决了这个问题。但本文将重点介绍 AMD 的 IBS 方法。

AMD 的 IBS 方法是为了解决这个问题而发明的。在继续之前,AMD IBS 计数器需要对处理器的流水线有基本了解。AMD 的 Zen 处理器是流水线的,这意味着每条指令都经过不同的计算阶段。对于 IBS 而言,这些阶段是获取(Fetch)、解码(Decode)、执行(Execute)、完成(Completion)和退休(Retirement)。

  • 获取(Fetch) 是核心从内存中获取指令的时候。处理器可能需要等待主内存,或者可能已经将指令放入 L1 缓存或 uOp 缓存中。
  • 解码(Decode) 是核心弄清楚指令的作用的时候。在理想情况下,AMD 处理器每时钟周期可以解码 4 条指令,或者从 uOp 缓存中发出 6 个 uOp。解码后,指令以 uOp 的形式保存在 uOp 缓存中,以加快将来的解码速度。
  • 执行(Execution) 是核心实际执行指令的时候。无论是除法、加法还是乘法。AMD Zen 核心有 10 个执行流水线:4 个整数流水线、4 个向量/浮点流水线和 2 个加载/存储单元(称为 AGU)。执行是高度流水线化和并行化的。
  • 完成(Completion) 是指令执行完毕后去向的地方。有些指令,如“xor eax, eax”,甚至无需进入执行核心即可立即完成。但其他指令,如除法,可能需要数十个时钟周期。完成是指令等待按正确顺序重新排序的地方。
  • 退休(Retirement) 是指令最终从处理器中移除的最后一个阶段。Agner Fog 声称 AMD Zen 每时钟周期可以退休 8 个 uOp。退休确保代码按原始程序员期望的正确顺序重新排序。

某些指令(如除法)在执行阶段可能需要很长时间。现代处理器将以“乱序”方式执行,并在等待慢速除法完成的同时尝试执行程序中找到的其他指令。仅当所有路径都依赖于早期指令时,CPU 才会停止。

对 AMD Zen 流水线有了粗略的了解后,我们现在可以理解 IBS 的工作原理。AMD 的 IBS 计数指令(或时钟周期:可配置)通过退休阶段。在 X 条指令(通常是 50000 或更高)之后,IBS 核心会标记流水线解码阶段的随机一条指令。然后,核心会收集这条标记指令直到退休的统计信息。一旦指令退休,就会生成一个中断,性能分析器就可以收集这些统计信息。

AMD Zen 核心需要两个不同的性能计数器来跟踪 IBS-Fetch 和 IBS-Ops。

性能分析你的代码

首先也是最重要的:在使用 IBS 采样之前,必须在 BIOS 中启用它。否则,您只能使用 PMC 计数器。以我的 Asrock x399 Taichi 主板为例,BIOS 设置在 Advanced -> AMD CBS -> Zen Common Options -> Enable IBS。不同的主板和版本可能会将此设置放在不同的位置。

幸运的是,设置 BIOS 是使用 IBS 采样的唯一棘手步骤。AMD uProf 下载和安装都很简单。GUI 简陋,统计数据的呈现并不总是直观或有用的,但所有重要信息都在里面。

https://developer.amd.com/amd-uprof/

我没有使用 Linux 版本。有一个粗糙的“API”,有两个值得注意的函数调用

bool amdProfileResume(AMD_PROFILE_CPU);

bool amdProfilePause(AMD_PROFILE_CPU);

在 GUI 和命令行中,都有选项可以暂停启动 uProf。这样,您的代码就可以启用和禁用性能分析器,以避免不相关的部分,例如 I/O。AMD uProf 手册提供了有关如何在 Visual Studio 和 GCC 中使用此 API 的指导。

值得注意的是,系统本身可以进行性能分析,这对于收集 L3 缓存统计数据或数据结构(DF)统计数据是必要的。总系统性能分析可能对那些调整软件的非程序员有用,Netflix 工程师曾就他们如何使用 Intel 硬件性能计数器来调整他们的服务器发表过一次精彩的演讲(http://www.brendangregg.com/linuxperf.html)。理论上,总系统性能分析可以在 AMD 系统上实现同样的效果。

关于统计数据的评论

这些统计数据的原始文档可以在 AMD 的 uProf 用户手册(https://developer.amd.com/wordpress/media/2013/12/User_Guide.pdf)或 AMD 的 Family 15h 软件优化指南(https://support.amd.com/TechDocs/47414_15h_sw_opt_guide.pdf)中找到。虽然现代 AMD Zen 核心是 Family 17h,但较旧的 Family 15h 是实际详细描述 IBS 的最新手册。

我将只重点介绍我认为对我的性能分析最重要的统计数据。

  • 所有 IBS op 采样 -- 所有指令的总数。在 IBS 模式下,请记住统计数据仅在标记的特定指令上生成。默认情况下,uProf 根据指令计数(例如:每 50,000 条指令)而不是时间来标记指令。这会产生一个不幸的效果:当代码优化得更好时(例如:每时钟周期执行约 2 条指令),它被默认标记的可能性就更高。尽管如此,这是大多数派生统计数据的“分母”,因此充分理解这一基本 IBS 统计数据的含义非常重要。
  • IBS 标记到退休周期 -- 特定指令完成所需的周期数。这是延迟的度量,而不是吞吐量的度量。指令可能会因为多种原因而延迟,从对其他指令的依赖,到等待内存响应。
  • IBS 完成到退休周期 -- 这个值与标记到退休周期相结合,可以用来确定是当前指令是罪魁祸首……还是“依赖”的指令在核心中停滞。
  • IBS 分支错误预测 op -- IBS 精确计算每个错误预测的 op。
  • IBS 数据缓存未命中加载延迟 -- IBS 精确计算任何指令等待内存的周期数。平均约 8 个周期的计数意味着数据在 L2 缓存中找到。平均约 40 个周期的计数意味着数据在 L3 缓存中找到。对于在主内存中找到的数据,平均约 150 个周期的计数是典型的。对于在远程 NUMA 节点(对于 Threadripper 或 EPYC 系统)中找到的数据,平均约 300 个周期的计数可能发生。
  • DTLB -- IBS 有许多统计数据来跟踪数据转换查找缓冲区(DTLB),这是核心内用于在现代操作系统中实现虚拟内存的加速结构。理论上,DTLB 问题可能会成为代码的瓶颈,特别是如果您在 RAM 中有大的 GB 级数据结构需要遍历。在我使用的场景中我还没有遇到过,但我可以想象其他一些程序员会遇到这个问题。AMD 提供了 L1 和 L2 DTLB 的统计数据,以及页遍历器统计数据。
  • IBS Fetch 采样 -- IBS Fetch 是与 IBS Op 采样不同的计数器。对于处理大量 MB 执行空间的程序员来说,指令获取本身可能成为瓶颈。AMD Zen 的获取仅发生在 64 字节边界上(与 L1 缓存大小相关)。

AMD uProf 性能分析器将这些统计数据汇总在一起,并按函数调用进行分组。

常见瓶颈和检测它们的 IBS 指标

  • 内存瓶颈 -- 关注 IBS 数据缓存未命中加载延迟统计。如果您的指令停滞了 150 个周期或更多,您的核心就在等待 RAM。您还会注意到在“乱序”完成的指令周围有较大的完成到退休周期,而对于那些停滞在 RAM 本身的指令,完成到退休周期会很小。尝试重写您的代码,以便在等待 RAM 时执行更多操作,例如执行“更复杂的计算”。遍历链表或树始终是内存密集型操作,但请利用核心的空闲周期在遍历过程中计算聚合统计数据。您在等待 RAM 时有空闲的 CPU 周期,不妨利用它们。在极端情况下,可以对数据结构本身运行压缩算法来提高性能。
  • 分支错误预测 -- 如今 OOP 很普遍,会有大量的对象、间接分支和虚函数调用。在某些情况下,分支错误预测本身会成为瓶颈(请参阅此 StackOverflow 问题了解一个著名的例子。在高度优化的代码中密切关注分支错误预测统计数据,这可以极大地影响性能!
  • 缓存外部执行 -- 许多基准测试代码都包含在 L1 缓存或 uOp 缓存中。然而,在实际程序中,需要遍历大量代码。并非所有循环都包含在缓存中。关注 IBS 获取延迟:如果获取指令需要很多很多周期,那么您将需要缩小代码规模(也许优化大小而不是速度),以便恢复到快速执行。

问题...

不幸的是,我对 IBS 性能分析的了解就到此为止。我个人仍然对 IBS 有很多疑问。

  • 是否可以通过 IBS 统计数据来衡量执行单元的利用率?PMC 计数器可以计算 FLOPs,例如,这对于确定指令是否利用了指令级并行性很有用。
  • PMC 统计数据“每时钟周期指令数”易于理解且很有用。到目前为止,我一直无法生成 IBS 的等效项。IBS 的“平均标记到退休周期”接近,但由于执行单元是乱序且并行执行的,因此此 IBS 指标仅衡量指令的延迟……而不是核心的吞吐量。
  • 是否可以通过 IBS 统计数据检测汇编代码中的长依赖链?如果是这样,它将提供一种度量适合循环展开的循环的方法。

对于这些情况,我仍然运行经典的 PMC 计数器来回答这些问题。然而,在使用 PMC 时,指令偏差是一个真正的问题,但它似乎是在 AMD CPU 上收集这些指标的唯一真正方法。

陷阱与权衡

主要的权衡是 IBS 的采样速度。采样的样本越多,统计数据就越好。然而,您收集的每个样本都是一个中断:CPU 暂停,切换到性能分析器,性能分析器写入大量信息,然后代码最终继续运行。因此,将采样速度设置得太高会影响程序的性能。反之:将采样速度设置得太低,会减少您收集的统计信息量,使得出关于代码性能的可靠结论变得更加困难。

启用更多计数器,例如 IBS Fetch、Cycles not in Halt(PMC 计数器)、#Instructions(也是 PMC 计数器)以及 IBS Ops,也会导致这些中断更频繁地运行,进一步影响程序的运行速度。如果您希望从性能分析器收集大量不同的计数器信息,请降低采样率。

我强烈建议学习如何使用其他性能分析器和计时器。Windows 有“QueryPerformanceCount”,其时钟频率约为 3MHz。在汇编级别,x86 / AMD64 指令集包含“rdtsc”和“rdtscp”指令。“rdtscp”特别有用,因为它会在返回时钟周期计数器之前清除流水线,从而提供更一致的性能读数。“rdtsc”以计算机的基本时钟速率运行,现代 CPU 不再会更改时间戳计数器以进行 Turbo Boost。

无论如何,使用这些替代计时器来生成一些粗略的计时估算。并确保将这些粗略估算与性能分析进行比较,包括有和没有性能分析。确保您正在收集的任何统计数据都与您的基本情况相匹配。

结论

虽然我最近才开始使用 IBS 采样,但很明显 IBS 采样提供了出色的延迟度量。这些延迟度量可以轻松地深入了解代码中内存问题(L2、L3 或主内存停滞)或分支预测问题的程度。

然而,我一直无法弄清楚如何使用 IBS 样本来深入了解经典的吞吐量度量指标:例如 IPC(每时钟周期指令数)、GFLOPs 或 GB/s 的 RAM 带宽。目前,IBS 似乎是一个有用的,但不完整的工具,用于收集特定的程序信息。

总的来说,在 AMD CPU 上进行性能分析的最佳方法似乎是首先使用经典的 PMC 计数器来收集整个程序的 IPC、GFLOPs 和 RAM 指标,以确定瓶颈通常存在于何处。然后,使用 IBS 进行第二次性能分析运行,可以对这些已识别的瓶颈的延迟特性提供更准确的、汇编级别的视图。

参考文献

历史

  • 版本 1.0.0.0 -- 首次发布
© . All rights reserved.