祉遛吾 发表于 2025-6-1 21:28:57

Arm Neoverse N1 Core: 性能分析方法

1 引言

随着许多 Arm 硬件和软件合作伙伴开发应用程序并将其工作负载移植到基于 Arm 的云实例上,Arm Neoverse 生态系统正在大幅增长。随着基于 Neoverse N1 的系统广泛普及,许多实际工作负载显示出了与传统系统相比极具竞争力的性能和显著的成本节约。最近的一些例子包括 H.264 视频编码、memcached、Elasticsearch、NGINX 等。
为了最大限度地提高执行性能,开发人员使用性能分析和工作负载特征描述技术来研究应用程序的性能特征。服务器类系统支持多种性能监控技术,可用于测量工作负载效率、评估资源需求和跟踪资源利用情况。此类测量有助于调整软件和硬件,也有助于指导未来的系统设计要求。
Arm Neoverse 微架构的开发需要考虑到高性能和低功耗。因此,我们的性能监控理念与软件开发人员分析基于其他架构的系统时所采用的理念略有不同。本文概述了利用 Neoverse N1 CPU 上的性能监控单元 (PMU Performance Monitoring Unit) 功能进行工作负载鉴定的方法,以识别和消除性能瓶颈。目标读者是从事软件优化、调优和开发工作的软件开发人员和性能分析人员。
本文内容分为四章:

[*]第一章介绍了 NeoverseN1 上的硬件 PMU,以及与工作负载特征描述最相关的 PMU 事件。
[*]第二章介绍使用 Neoverse N1 内核 PMU 事件进行工作负载鉴定的方法。
[*]第三章说明了如何使用 Linux perf 工具收集 Neoverse N1 PMU 事件。
[*]最后一章通过一个工作负载案例研究,展示了工作负载特征描述和热点分析。
2 Neoverse N1 性能监控事件

性能监控需要收集特定系统上的应用执行信息,这些信息可以通过软件或硬件手段获得。软件监控技术提供软件跟踪和系统软件统计的事件。硬件监控技术则直接从 CPU/系统中收集硬件事件。为了收集硬件事件,现代处理器采用了专用的性能监控单元(PMU),便于测量各种硬件执行相关事件。可以监控多个事件,并将其与软件执行相关联,以确定优化机会,评估工作负载是否以最佳方式利用了底层微体系结构。支持的部分事件包括指令退役(退休)、CPU 运行周期、缓存/TLB 访问和分支预测。
2.1 Arm 性能监控单元

Arm 架构通过名为性能监控器扩展(PES: Performance Monitors Extension)的可选扩展支持 PMU 功能。Arm PMU 硬件设计由以下组件组成:

[*]用于控制和事件选择的 PMU 配置寄存器
[*]PMU 事件计数器
[*]专用功能计数器

ArmPMU 硬件采用多个配置寄存器,包括 PMCR 和 PMEVTYPER 寄存器,分别用于单元控制和测量选择。PMU 硬件还包含一组事件计数器,用于根据用户要求对原始硬件事件进行计数。每个事件都有一个由硬件供应商设计的与之相关的十六进制事件代码,并将其设置在 PMEVTYPER(Performance Monitor Event Type Register) 寄存器中。除了可配置计数器外,AArch64 还为 CPU cycle提供了专用计数器,这是所有 Arm 兼容设计所必需的功能。PMU 硬件的实现与可用计数器的数量和可计数的事件类型(包括相应的事件代码)有关。
当配置 PMU 事件时,其中一个事件计数器将被分配到与被测事件相关的数字逻辑上。PMU 事件可使用 Linux perf 工具测量,既可使用软件列出的常见 “硬件事件 ”的事件名称,也可使用与架构指定的 “PMU 硬件事件 ”相关的专用事件代码。典型的处理器可能支持数百个用于性能和调试目的的事件,但可以选择一个子集进行工作负载特征描述,以研究高级执行瓶颈和资源利用情况。针对子系统单元的其他特定事件可用于深入分析性能问题,以及从特性分析中确定的性能限制单元。
2.1.1 Arm PMU 实现参考

这些事件的行为在各自的《Arm 架构参考手册 》中被定义为 “通用事件”。通用事件 "是通用定义,适用于所有微体系结构,但大部分是软件需求,不一定能实现。
2.2 Neoverse N1 性能监控单元

Neoverse N1 CPU 实现了 Arm v81 的 PMU 扩展,支持 100 多个硬件事件。Neoverse N1 PMU具有6个可配置计数器和1个专用计数CPU cycle的功能计数器。
Neoverse N1 内核实现的 PMU 事件已在 ARM Neoverse N1 内核技术参考手册(TRM)D2 部分中列出。除 TRM 外,我们还提供了 Neoverse N1 PMU 指南,这是有关内核实现的硬件 PMU 事件的补充指南。该 PMU 指南详细描述了按 CPU 模块分类的 PMU 事件。
为了更好地理解每个 PMU 事件,本指南还包括了微体系结构和体系结构所需的定义,并在每个 PMU 事件的描述中标注了相关定义作为参考。
我们建议将《N1 PMU 指南》作为使用 PMU 事件进行性能分析活动的事件描述参考手册。
2.3 Neoverse N1 Core PMU 工作负载特征描述事件表

尽管Neoverse N1 Core支持 100 多个硬件计数器,但并非所有计数器都需要用于工作负载执行的初始特征描述。下图是用于 Neoverse N1 CPU 首次工作负载特性分析的主要性能监控事件清单。

3 Neoverse N1 性能分析方法

如今,性能优化和软件调整比以往任何时候都更具挑战性。现代高端处理器通常具有高内核数、复杂指令集、不同程度的并行性和深层内存层次结构。此外,大规模云工作负载中有很大一部分是在虚拟机上运行的,这使得追踪工作负载的执行行为以评估数据中心的电源性能效率变得更加困难。因此将软件执行与微体系结构行为关联起来,对于优化软件在底层硬件上的高效执行至关重要。
单核构成了处理器的基本执行块。这些内核通过多个配置和子系统组合在一起,形成一个计算系统。在本章中,我们将探讨单个 Neoverse N1 内核的高级细节,以及使用内核的硬件 PMU 事件进行工作负载鉴定的方法。
3.1 Neoverse N1 Core

Neoverse N1 Core是一个无序超级标量机,每个周期最多可分派/退役 8 条指令。超级标量处理器的流水线有三个主要阶段:

[*]指令流的按序取指和解码称为前端
[*]无序执行部分称为后端。
[*]有序提交/退休
CPU有一个内存子系统,负责所有内存操作及其有序执行。所有这些主要 CPU 模块的设计细节因微体系结构而异。
在本文档中,我们将介绍大多数超标量架构中常见的模块,并强调适用于 N1 实现的相关性能指标。有关 Neoverse N1 微体系结构的详细说明,请参阅《Neoverse N1 技术参考手册》和《Neoverse 软件优化手册》。下图显示了 Neoverse N1 CPU 的高级框图。

3.1.1 前端

CPU 的前端是一条按顺序排列的流水线,用于处理从 I-Cache 抓取指令、对这些指令进行解码以及为后端执行引擎排队。在解码阶段,架构指令可分解为微操作。这些微操作根据其可用性排队并分派给执行引擎。除了获取、解码和调度单元外,还有一个重命名块,用于跟踪操作数据流和依赖关系,确保已执行的操作按顺序提交。前端的另一个重要单元是分支预测器。该单元可预测分支的方向以及间接分支的目标地址。分支预测技术有助于尽可能按照正确的程序顺序获取指令,因为分支错误预测会导致管道刷新和周期浪费。Neoverse N1 每个周期最多可获取 4 条指令,最多可进行 8 次微操作。
操作。
3.1.2 后端

CPU 的后端处理微操作的执行,这些微操作被分派到相关执行单元进行处理。Neoverse N1 支持多个执行单元,包括分支单元、加载/存储单元(load/store)和算术单元(包括高级矢量引擎 (advanced vectorengines))。执行单元的数量和执行一条指令所需的周期因微体系结构而异;因此,指令执行的延迟和吞吐量取决于实施情况。指令执行完成后,会按顺序存储并提交相关结果。
Neoverse N1 有 4 个整数执行单元、2 条浮点/SIMD 流水线和 2 条加载/存储流水线,因此每个周期最多可向执行流水线发送 8 个微操作。
3.1.3 内存子系统

CPU 的内存子系统处理加载和存储操作的执行,这在很大程度上依赖于内存的层次结构。Neoverse N1 每个内核都有一个专用的 L1/L2 高速缓存,其中 L2 高速缓存由 L1 数据高速缓存和 L1 指令高速缓存共享。加载存储单元控制高速缓存和内存之间的数据流。
Neoverse N1 有两个加载/存储单元,可同时处理读写操作。L1 数据高速缓存是一个 64kB 的 4 路关联集设计,L2 高速缓存是一个 8 路关联集高速缓存,最大容量为 1MB,可根据实现情况进行配置。内核的专用二级缓存通过 AMBA 5 CHI 接口连接到系统的其他部分。

[*]Neoverse N1 群集配置
Neoverse N1 系统有不同的配置,具体取决于互连和群集/系统级末级高速缓存的实施选择。图4和图5显示了 NeoverseN1 系统中的一些可选配置。图 4 显示了一种配置,在这种配置中,多个内核可以配置为基于动态共享单元(DSU)的集群系统,单个集群中包含两个内核。该 DSU 群集包含一个可选的 L3 高速缓存,可由群集内的内核共享。这种可选的 L3 高速缓存在设计上最大可达 2 MB。

另一种配置是图 5 所示的直接连接系统,顾名思义,内核直接连接到称为 CAL 的相干网格互连接口。这些系统不支持 L3 高速缓存,因为没有 DSU 集群。

所有采用相干网状互连的系统都支持共享的系统级高速缓存,其大小可达 256MB。了解所分析系统的高速缓存结构和配置,对于从高速缓存有效性 PMU 事件中获得洞察力至关重要。最好向提供商了解包括缓存大小在内的底层系统的系统配置详情。
3.2 性能分析方法

对于工作负载分析,原始硬件事件以及从中得出的一些有用指标都可用于鉴定。要理解所有事件并决定使用哪些事件并非易事,因为每个工作负载都有独特的行为和潜在瓶颈。为了简化这一过程,我们将重点介绍一些有助于对工作负载进行初步描述的原始事件和衍生指标。遵循这一流程将有助于制定工作负载的顶层特征,并从根本上解决深度挖掘流程中的性能问题。
可遵循的基本顶层分析方法首先是计算执行周期的使用情况,如图 6 所示。
3.2.1 IPC(Instructions Per Cycle)每周期指令数

消耗的cycle数和执行的指令数量为任何体系结构上的 CPU 工作负载和执行时间提供了概况。指令数量代表 CPU 所做的工作量,而cycle代表完成这些工作所需的总时间。每周期指令数(IPC)是评估微体系结构工作负载性能的一个关键指标,其推导过程如下:

在非停顿(non-stalled)流水线中,IPC 将是最高的,可以直接监测处理器支持的指令级并行性。IPC 达到的越高,执行过程中流水线的效率就越好。如果 IPC 非常低,这意味着花费的cycle大量停顿,这可能导致潜在的性能问题。Neoverse N1 流水线的最大 IPC 为 4,因为它每个周期可以获取 4 条指令。
在指令计数方面,Neoverse N1 支持 INST_RETIRED 和 INST_SPEC 事件,这两种事件都计算已执行的指令,但处于流水线的不同阶段。
INST_RETIRED 计算的是程序中按架构执行的指令总数,而 INST_SPEC 计算的是向处理器推测解码的指令总数。INST_SPEC 可以更好地说明执行单元的总利用率,因为它计算的是本可以执行但不一定提交的指令。INST_SPEC 和 INST_RETIRED 之间的巨大差异通常表明分支错误预测率很高,或者其他故障会取消已编码的投机指令,这是不高效的。
请注意,工作负载的 INST_RETIRED 在架构的所有实现上都是相同的,而周期则因微架构实现和系统配置(是否连接到云)而异。
3.2.2 周期计数/流水线停顿(Cycle Accounting/Pipeline Stalls)

无序CPU有一个有序前端单元,负责获取指令并将其解码为微操作,然后下发到后端执行。前端的取指单元从 L1I 高速缓存中获取指令,这需要访问 L1I TLB (一级指令转换后备缓冲器 Level 1 Instruction Translation Lookaside Buffer)以获取物理地址,还需要一个分支预测单元来推测性地获取后续指令。虽然后端可以不按顺序执行微操作,但所有指令都是按顺序执行的。
上述流水线可实现高吞吐量的指令执行。但是,流水线中可能会出现停顿,原因有很多,例如由于分支预测错误或等待内存或 L2/L3 高速缓存中的数据而获取错误的代码路径。此外,执行过多的错误路径代码会减少 CPU 的有用周期数。为了评估管道执行的效率,NeoverseN1 支持多级 STALL 事件,以评估 CPU 前端和后端停滞的周期数。

STALL_FRONTEND 和 STALL_BACKEND 事件可用于显示工作负载执行过程中的主要瓶颈。利用这两个事件,我们可以分别得出前端和后端停滞周期的相对百分比:

相对较高的前端停滞率表明,周期浪费是由于前端内序分部的流水线停滞造成的,而相对较高的后端停滞率表明,周期浪费是由于后端的流水线停滞造成的。这种细分有助于缩小主要区块的范围,进一步分析这些区块以确定性能瓶颈。
以下是为进一步分解前端停顿工作负载而分析的 CPU 块/单元列表:

[*]ITLB 事件
[*]I-Cache 事件: L1I + L2/Last 级缓存事件
[*]分支效率事件(Branch Effectiveness events)
以下是为进一步分解后端停顿工作负载而分析的 CPU 块/单元列表:

[*]DTLB(数据转换后备缓冲器:Data Translation Lookaside Buffer)事件
[*]内存系统相关事件
[*]D-Cache缓存计数器: L1D + L2/末级缓存事件
[*]指令混合
N1 PMU 指南详细介绍了可用于上述各 CPU 块的所有原始硬件事件。接下来,我们将介绍 N1 PMU CheatSheet[第 2 章]中推荐的部分事件,包括可用于描述 CPU 块特性的一些指标。
3.2.3 分支有效性(Branch Effectiveness)

分支调度预测在流水线化的 CPU 中成本很高,会导致频繁的流水线刷新和周期浪费。一般来说,工作负载通常平均每 6 条指令中包含 1 个分支。虽然现代 CPU 拥有优化的分支预测单元,但仍有许多使用案例,如光线跟踪、决策树算法等,分支繁多且难以预测。在其中一些应用中,可能会有数百条独特的分支路径,而且目标可能与输入数据有关。
分支预测性能可通过两个原始 PMU 事件 BR_MIS_PRED_RETIRED 和 BR_RETIRED 进行评估。BR_MIS_PRED_RETIRED 显示了已执行但被错误预测的分支总数。这意味着错误预测的基本代码块中的代码路径方向是错误的,路径中的后续操作被浪费了,导致管道堵塞。
可以得出两个性能指标,用于高水平评估分支执行性能与整个程序执行的关系:

分支 MPKI 提供每千条指令错误预测的分支总数,而分支错误预测率则显示错误预测的分支与整体分支的比率。这两个指标都可用于使用 perf 记录进行进一步调查,以确定是哪些函数导致了分支未命中率的增加 [第 4 章]。
3.2.4 分支混合/预测性能(Branch Mix/Prediction Performance)

分支预测单元根据分支类型以不同方式工作。主要有三个部分

[*]分支历史表 (BHT Branch History Table),用于存储已执行或未执行的条件分支的历史记录。
[*]分支目标缓冲区(BTB Branch Target Buffer),用于存储间接分支的目标地址
[*]返回地址栈(RAS Return Address Stack),用于存储函数返回分支。
Neoverse N1 支持三个事件:BR_IMD_SPEC、BR_RETURN_SPEC 和 BR_INDIRECT_SPEC,分别用于对执行的立即、间接和返回分支进行分类。对分支类型进行细分有助于深入了解分支预测单元中每个子模块的性能。请注意,这些事件同时计算正确预测和错误预测的分支。不支持只计算错误预测分支的相应事件。在 Neoverse N1 上,Statistical ProfilingExtensions(SPE)可用于将分支预测错误归因于单个分支,从而提供比 PMU 事件更有针对性的分析。
3.2.5 TLB/MMU 效能(TLB/MMU Effectiveness)

另一个重要的性能评估步骤是检查虚拟内存系统的性能,它影响前端的指令取回性能和数据端的内存访问性能。处理器在访问相应的高速缓存之前,需要将任何指令/数据内存访问的虚拟地址转换为物理地址。请注意,程序对内存的看法是虚拟地址,但处理器在访问高速缓存或内存时使用的是物理地址。

虚拟地址和物理地址在系统内存中的页转换表中定义。访问这些表需要一次或多次内存访问,需要多个周期才能完成--这被称为页表走行。不过,为了加快翻译速度,翻译查找边缓冲区(TLB Translation Lookaside Buffers)会缓存翻译表的走行,从而大大减少对系统内存的访问次数。
第一层包含用于指令和数据(加载/存储)地址转换的独立专用 TLB。对这些 TLB 的总访问量分别由 L1I_TLB 和 L1D_TLB 计算。第二层包含一个统一的 L2TLB,由 I 侧和 D 侧访问共享。有相应的 REFILL 计数器,对这些 TLB 层中的填充进行计数。由于 I 侧和 D 侧 TLB 中的未命中而导致页表走行的访问分别通过 ITLB_WALK 和 DTLB_WALK 事件进行计数。
为了评估 TLB 的有效性,可以从原始事件中得出四个指标:

ITLB MPKI 和 DTLB MPKI 分别提供了指令和数据访问中每千条指令的 TLB 走行率。DTLBWalkRate 提供了 DTLBWalks 与程序总体 TLB 查找次数的比率。请注意,这与 DTLB_WALK/ MEM_ACCESS 相同。MEM_ACCESS 相同,因为每次 MEM_ACCESS 都会导致一次 L1D_TLB 访问。ITLB 走行率提供了 ITLB 走行占指令端启动的 TLB 查找总数的百分比。
3.2.6 指令混合(Instruction Mix)

NeoverseN1 微体系结构有 8 个执行单元,可以处理五种类型的操作:分支、单周期整数、多周期整数、带地址生成功能的加载/存储单元以及高级浮点/SIMD 操作:

[*]LD_SPEC: 已发出的加载指令
[*]ST_SPEC: 已发出的存储指令
[*]ASE_SPEC: 已发出的高级 SIMD 指令
[*]VFP_SPEC: 已发出的浮点指令
[*]DP_SPEC:已发出的整数数据处理指令
[*]BR_IMD_SPEC:已发出的立即分支指令
[*]BR_INDIRECT_SPEC:已发出的间接分支指令
[*]BR_RETURN_SPEC:已发出的返回分支指令
请注意,这些都是推测执行的计数,因为这些指令是在发出阶段计数的,可以估算执行单元的利用率,但不能估算程序的退役指令组合。Neoverse N1 不支持用于计算架构指令组合的退役事件计数器。Neoverse N1 支持通过事件 BR_IMD_SPEC、BR_INDIRECT_SPEC 和 BR_RETURN_SPEC 将分支操作进一步细分为立即分支、间接分支和返回分支。这三个分支操作事件的总和可用于计算总分支。要评估 CPU 执行单元的负载,最好是根据 INST_SPEC 计数器(该计数器计算发出的执行指令总数)得出各类操作的百分比。
举例说明:

3.2.7 CPU核心内存流量(Core Memory Traffic)

MEM_ACCESS事件计算CPU核的Load Store Unit(LSU)发出的内存操作总数。由于这些操作首先在L1D_CACHE中进行查找,因此L1D_CACHE和MEM_ACCESS事件数量相同。Neoverse N1 还支持另外两个事件 MEM_ACCESS_RD 和 MEM_ACCESS_WR,这两个事件可以分别提供读写流量的分解。请注意,这些事件与LD_SPEC和ST_SPEC不同,因为它们计数issue出去的内存操作,但不一定执行。
3.2.8 缓存有效性(Cache Effectiveness)


[*]第一级(L1)包括一个专用于指令的高速缓存和一个单独的专用于数据访问的高速缓存。
[*]第二级(L2)是统一的二级缓存,代码和数据共用。
[*]再往下,系统可在内核集群中设置可选的 L3 高速缓存,并在互连中设置可选的共享系统级高速缓存 (SLC)。L3 和 SLC 高速缓存是实施选项。
Neoverse N1 内核支持所有缓存层次结构级别的分层 PMU 事件。请注意,ARCH64 不支持高速缓存未命中计数器,只支持 REFILL 计数器。高速缓存未命中可能会导致多个高速缓存链的填充,如果高速缓存的访问触犯了高速缓存链的边界,那么多个高速缓存未命中就会通过一次 REFILL 来满足。有关高速缓存事件计数器描述的详细信息,请参阅 N1PMUGuide3。缓存策略和关联性详情也可参阅《N1 PMU 指南》中的 “微体系结构 ”一章。
对于内核的所有高速缓存层次,可以得出一组有用的指标来研究高速缓存行为。例如,L1 数据高速缓存指标可导出如下:

3.2.9 缓存重填特性分析(Cache REFILL Characterization)

对于带有 DSU 集群的 Neoverse N1 系统,N1 还支持两种高速缓存 REFILL 变体,可用于测量高速缓存是否从集群内部或集群外部填充数据。
3.2.10 远程高速缓存访问(Remote Cache Access)

对于具有多个套接字或 SOC 的 Neoverse N1 系统,N1 支持 REMOTE_ ACCESS 事件,该事件可统计由来自其他芯片的数据源处理的内存事务。
3.2.1 末级高速缓存计数器的使用(Last Level Cache Counter Usage)

正如我们在 [第 3 章] N1 系统配置一节中所看到的,NeoverseN1 系统可以支持集群级 L3 高速缓存和共享的系统级高速缓存。Neoverse N1 为 L3 和 LL(最后一级)实现了两套缓存分级事件。
L3 高速缓存是一种可选高速缓存,只有内核实现了 L3 高速缓存,才会计算相应的事件。但是,如果系统配置为双核集群系统,则此事件可能会计算集群内窥探产生的对等缓存流量。[有关详细信息,请查阅您的 SOC 规范。]
在支持共享系统级高速缓存的系统中,LL_CACHE_RD 会统计对 SLC 的总访问量。在配置了 SLC 以统计 LL_CACHE_RD 事件的系统中,LL_CACHE_RD 计数器会统计内核对 SLC 的总访问量,而 LL_CACHE_MISS_ RD 会统计在 SLC 上错过的访问量。
为了研究末级读取行为,可以得出末级缓存读取未命中指标:

衡量读取流量的 SLC 命中率的另一个有用指标是 SLC 读命中率。在 Neoverse N1 中,末级缓存事件没有写入变量,因为 SLC 仅用作内核的驱逐缓存。

4 使用 Linux Perf 工具进行性能分析

Linux perf 是一种广泛使用的开源工具,用于从硬件和系统软件的不同来源收集软硬件性能事件。内核采用 perf_event 子系统收集可测量事件,包括来自处理器本身的硬件 PMU 事件。如图所示,每个内核都有自己专用的 PMU 硬件,内核 perf 驱动程序分别从每个内核 PMU 收集事件。

Linux perf_event 系统为 Linux 内核和用户空间性能监控工具提供了一个接口,以便根据需要收集原始硬件事件。Linux perf 工具是 Linux 中的开源工具,可用于性能监控,它支持两种测量技术:

[*]计数(Counting): 计数收集工作负载执行过程中事件的总体统计数据,其中为每个事件分配的计数器会产生总体事件计数的总和。这种事件统计有助于描述整体工作负载的执行行为,但不提供特定事件在程序中哪个位置发生的任何细节。这种方法是初始工作负载特征描述练习的最佳方法,可用于识别工作负载的性能限制。
[*]基于事件的采样:事件采样是一种通过配置PMU 计数器,使其在预先设置的事件数之后溢出,从而对事件进行采样的方法。有了这些数据,就很容易找到造成大部分采样事件的库和代码部分。
Linux perf 工具在计数和事件采样模式下的所有上述性能测量技术都具有以下功能:

[*]stat:提供程序整体执行的性能计数器统计数据
[*]record:记录每个库和函数中每个事件的采样百分比的执行性能
[*]report:使用 record 生成所记录采样的报告
[*]annotate:在拆解代码时对报告中的采样百分比进行注释
在需要高精度时,例如在对热循环或代码的重要部分进行预处理时,应首选 “计数 ”模式,以确保其准确性--但需要注意的是,在必须对多个不同事件进行记录时,可能需要进行多次配置文件提取。 对于 Neoverse N1 CPU,最好一次最多计数 6 个事件,以便为每个事件设置一个专用计数器。当事件数多于可用计数器总数时,计数器将在事件之间进行多路复用,并根据总时间段对最终计数进行缩放。这种多路复用计数可能会导致精度问题,但通常是可靠的,除非需要精确测量。
事件采样模式对于大部分代码的热点分析非常有用,它依赖于统计方法对大部分时间或代码中的不同事件进行采样。需要注意的是,这种方法有一些局限性,会导致准确性问题。采样延迟,即计数器溢出和中断处理之间的延迟,会导致获取的数据出现偏差,也就是说,采样过程中存储的数据可能不是事件发生的准确点。另一个问题来自于处理器的推测执行方式,如果某些指令在错误的代码路径上执行并触发了事件,那么这些指令可能无效。虽然这种方法在准确性上有一定的局限性,但仍然是接近识别代码执行热点的最佳方法。
Linux perf 允许调整采样频率,如果数据在不同的运行中显示出很大的不一致性,这有助于研究事件计数的变化。
4.1 使用 Linux Perf 工具收集 Arm 架构的硬件 PMU 事件

为了启用 PMU 事件收集功能,在构建 Linux 内核时必须在内核配置中启用 CONFIG_HW_PERF_EVENTS,大多数产品都启用了此配置选项,但如果您正在编译自定义内核,请记得启用此配置选项。此外,还需要以 root 用户身份配置两个系统设置,以便获得内核符号表和额外的特权。

perf_event_paranoid 控件会影响内核中的权限检查,将其设置为 -1 会允许打开可能泄露敏感信息或影响系统稳定性的事件。详情请查看内核文档。
kptr_restrict 控件影响内核地址是否公开(例如通过 /proc/kallsyms)。一些开发人员在没有 vmlinux 的情况下(或在使用 KASLR 的情况下)使用这种技术来获取内核符号解析。请参阅内核文档中关于 kptr_restrict 的详细信息。
在所有这些情况下,都存在潜在的安全隐患,因此在启用它们之前,请检查官方内核文档并咨询系统管理员。
使用 Linux perf 工具的 perf stat 功能来计算指令和周期,是验证 PMU 事件计数是否正确的简单测试方法。在 Arm 架构上,0x8 是退役指令的十六进制代码,0x11 是 CPU 周期的十六进制代码。
这些事件提供给 perfstat-e 选项,后缀为 “r”。
# perf stat -a -e r8,r11 sleep 10

Performance counter stats for 'system wide':

   105,767,217,553      r8
    51,378,856,540      r11

      10.006758810 seconds time elapsed上述命令将计算 10 秒钟内所有 CPU 的指令总数和 CPU 周期数。Linux perf 允许对特定 CPU、每个进程、每个线程等进行计数,这可以在 perf stat man 页面找到。
请注意,如果某个事件不支持或未启用,perf 有时会无声地失败。请咨询 CPU TRM,了解系统对事件的支持情况。
以下章节中的示例使用原始事件编号来说明应监控哪些事件。不过,Linux 内核 4.17 及更高版本支持使用命名值访问 N1 内核 PMU 事件。对于早期版本,必须使用原始事件编号。有关将命名事件映射到编号的信息,请参阅《Arm Neoverse N1 PMU 指南》。对于 PMU 事件测量的自动化工具,Arm 在 ARM-Software/ PMU-Data 的 Github 存储库中提供了包含所有事件和事件代码的机器可读 JSON 文件。
4.2 使用计数模式收集硬件PMU事件

对于计数模式,请使用“perf stat ”命令,如下所示。
要统计所有事件以进行特征描述,典型的解决方案是利用平台中可用的总计数寄存器分批捕获事件。
# perf stat -e r8,r11 sleep 10

Performance counter stats for 'sleep 10':

         864,830      r8
         1,461,792      r11

      10.001077627 seconds time elapsed

       0.001026000 seconds user
       0.000000000 seconds sys4.3 使用采样模式收集硬件PMU事件

对于采样事件,可以使用Linux Perf工具中的“perf record”命令,如下所示。
有关如何使用这些命令行进行采样和分析采样数据的更多详细信息,请参阅这些Linux perf示例。
# perf record -e instructions,cycles -- sleep 5
# perf report
# perf annotate一旦使用计数模式收集事件并按照第3章中概述的方法进行工作负载特征分析,就可以从中筛选出一部分事件进行采样和热点分析,如以下例子所示。
5 Neoverse N1 上的性能分析:案例研究

在本节中,我们将演示如何使用第 3 章中概述的性能分析方法,通过使用 Linux perf 从 Neoverse N1 系统捕获的核心 PMU 指标进行工作负载特征描述和热点分析。我们将介绍一个在 Neoverse N1 软件开发平台(N1SDP)上运行的工作负载特性案例研究示例,该平台有 4 个 Neoverse N1 内核。我们使用 Linux Perf 工具 “perf stat ”一次收集 6 个批次的 PMU 事件。
5.1 案例研究: DynamoRIO Strided Benchmark

在案例研究中,我们运行了 DynamoRIO 测试中的 Stride Benchmark。stride 微基准是一个指针追逐基准,它访问 16MB 数组中的值,数组位置由被追逐的指针决定。指针位置是指针追逐内核运行前在数组中设置的常量值的函数。
实验环境1设置

实验环境2设置
# lscpu
Architecture:         aarch64
CPU op-mode(s):       64-bit
Byte Order:         Little Endian
CPU(s):               128
On-line CPU(s) list:0-127
Vendor ID:            HiSilicon
BIOS Vendor ID:       HiSilicon
Model name:         Kunpeng-920
...

# cat /etc/os-release
NAME="KylinSec OS"
VERSION="3 (Qomolangma)"
ID="kylinsecos"
ID_LIKE="openeuler"
VERSION_ID="3"
PRETTY_NAME="KylinSec OS Linux 3 (Qomolangma)"
ANSI_COLOR="0;31"
HOME_URL="https://www.kylinsec.com.cn"
BUG_REPORT_URL="https://support.kylinsec.com.cn"

[*]dynamorio下载
[*]stride_benchmark.cpp
5.1.1 第 1 阶段:使用计数模式鉴定工作负载

IPC 是评估总体工作负载执行效率的第一个严格指标。

观察说明(表 1): 0.22 的 IPC 远低于 Neoverse N1 上大多数工作负载的测量值。与之相比,SPEC CPU(r) 201711(估计值)等旨在对系统施加压力的大型基准工作负载至少能在该 CPU 上实现平均 IPC > 1。在 Neoverse N1 上可实现的最大 IPC 为 4,这通常是由小型和经过大量优化的内核而非大型应用程序实现的。
环境2:
]# perf stat -e instructions,cycles/root/code/DynamoRIO-AArch64-Linux-11.3.0                                                                              -1/bin64/drrun   -t drmemtrace -tool miss_analyzer -LL_miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stri                                                                              de_benchmark
Value = 8960000
--------
Cache miss analyzer results:
pc=0x4000052aec70, stride=384, locality=nta
pc=0x4000052aefd8, stride=384, locality=nta
pc=0x4000052ae934, stride=128, locality=nta
pc=0x4009f0, stride=448, locality=nta

Performance counter stats for '/root/code/DynamoRIO-AArch64-Linux-11.3.0-1/bin64/drrun -t drmemtrace -tool miss_analyzer -LL_                                                                              miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stride_benchmark':

   6,690,869,531      instructions            #    1.68insn per cycle
   3,982,991,044      cycles

       1.294693889 seconds time elapsed

       1.482033000 seconds user
       0.048367000 seconds sys获得的 IPC 值为 1.68

[*]周期计数(Cycle Accounting)

作为确定工作负载性能瓶颈的第一步,查看周期计数相关事件所花费周期的分布情况。

总cycle的83%在后端停顿,0%在前端停顿,这占用了由流水线停顿浪费的总体执行时间的83%。由于工作负载严重绑定后端,我们现在将逐一分析后端事件。我们已经从循环指针追逐代码中得到了可能是内存绑定的提示,在这种情况下,指针追逐代码本质上是对 CPU 缓存的访问。
接下来,我们将查看缓存性能和指令组合,因为它们可以揭示是否有什么因素导致我们的应用程序成为核心绑定,或者我们是否存在缓存性能问题。
在环境2上结果如下:
# perf stat -e cycles,stalled-cycles-frontend,stalled-cycles-backend,BR_RETIRED /root/code/DynamoRIO-AArch64-Linux-11.3.0-1/bin64/drrun   -t drmemtrace -tool miss_analyzer -LL_miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stride_benchmark
Value = 8960000
--------
Cache miss analyzer results:
pc=0x400011bb8c70, stride=384, locality=nta
pc=0x400011bb8fd8, stride=384, locality=nta
pc=0x400011bb8934, stride=128, locality=nta
pc=0x4009f0, stride=448, locality=nta

Performance counter stats for '/root/code/DynamoRIO-AArch64-Linux-11.3.0-1/bin64/drrun -t drmemtrace -tool miss_analyzer -LL_miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stride_benchmark':

   3,982,550,552      cycles
   1,469,978,223      stalled-cycles-frontend   #   36.91% frontend cycles idle
       168,951,085      stalled-cycles-backend    #    4.24% backend cycles idle
   1,526,351,269      BR_RETIRED

       1.295596696 seconds time elapsed

       1.477747000 seconds user
       0.052878000 seconds sys可见环境2中,主要是前端受限。

[*]数据缓存(D-Cache)效率

由于 L2 是与 L1D-Cache 和 L1-ICache 统一的缓存,因此 L2 和最后一级缓存的未命中也可能是由前端的指令未命中引起的。在评估缓存性能时,检查 L1-I 错失次数总是有意义的。不过,对于这种工作负载,我们预计不会出现 L1-I miss,因为前端的滞留可以忽略不计。
下表列出了一些详细的缓存效率指标:

L1D 缓存 MPKI 显著高达 106,其中 53% 的 L1D 缓存访问都是重新填充。二级缓存 MPKI 高达 78,53% 的访问需要重新填充。此外,最后一级缓存读取 MPKI 对内存带宽资源造成了巨大压力,因为 99% 的读取请求没有得到满足,需要将LL 请求发回域内存。由于没有 L1-I miss,二级缓存和末级缓存的压力仅来自后端内存系统。
接下来让我们看看指令组合,了解内存指令的执行比例。
# perf stat -e cycles,stalled-cycles-frontend,stalled-cycles-backend,BR_RETIRED,L1D_CACHE,L1D_CACHE_REFILL,L2D_CACHE,L2D_CACHE_REFILL,LL_CACHE_RD,LL_CACHE_MISS_RD /root/code/DynamoRIO-AArch64-Linux-11.3.0-1/bin64/drrun   -t drmemtrace -tool miss_analyzer -LL_miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stride_benchmark
Value = 8960000
--------
Cache miss analyzer results:
pc=0x40003a1fdc70, stride=384, locality=nta
pc=0x40003a1fdfd8, stride=384, locality=nta
pc=0x40003a1fd934, stride=128, locality=nta
pc=0x4009f0, stride=448, locality=nta

Performance counter stats for '/root/code/DynamoRIO-AArch64-Linux-11.3.0-1/bin64/drrun -t drmemtrace -tool miss_analyzer -LL_miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stride_benchmark':

   3,987,700,360      cycles
   1,466,330,193      stalled-cycles-frontend   #   36.77% frontend cycles idle
       176,866,560      stalled-cycles-backend    #    4.44% backend cycles idle
   1,527,527,743      BR_RETIRED
   2,701,320,530      L1D_CACHE
         6,651,603      L1D_CACHE_REFILL
      49,966,737      L2D_CACHE
      10,186,517      L2D_CACHE_REFILL
         7,861,886      LL_CACHE_RD
         1,358,060      LL_CACHE_MISS_RD

[*]指令混合


指令组合显示整数操作占 60%,加载指令占 20%,分支指令占 20%。由于我们的工作负载中存在明显的设计缺陷,这说明该工作负载是内存受限的,需要进行优化以改善其缓存压力,从而提高性能。
虽然该工作负载不受限于前端,但仍值得检查分支有效性计数,因为该工作负载也包含 20% 的分支。
kperf收集指令混合数据
# /home/qatest/script/kperf/kperf --imix --machine kunpeng920b /root/code/DynamoRIO-AArch64-Linux-11.3.0-1/bin64/drrun   -t drmemtrace -tool miss_analyzer -LL_miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stride_benchmark
architecture kunpeng920b
============== Instruction Mix ===============
ld_mix:                                 25.97%
st_mix:                                 13.14%
dp_mix:                                 37.83%
ase_mix:                                 0.07%
vfp_mix:                                 0.11%
br_imm_mix:                           17.82%
br_ret_mix:                              3.66%
br_ind_mix:                              5.06%
strex_fail_mix:                        0.00%
crypto_mix:                              0.00%
svc_mix:                                 0.00%

[*]分支效率

为评估分支预测效果,下表列出了错误预测率和 MPKI 指标:

该工作负载的分支预测错误率可忽略不计。否则,我们会看到前端停滞;这在意料之中。
由于我们已经确定了工作负载的内存边界,因此让我们来看看 L1I TB 的性能如何。我们预计不会出现 L1I TLB 性能问题,因为我们的前端延迟可以忽略不计。

[*]TLB效率

TLB 效能 为了评估 TLB效率,我们统计了 MPKI 指标:

指令侧 TLB 错失可以忽略不计,而数据侧 TLB 的走表数显著增加。这表明工作负载确实会产生数据侧页面未命中,从而导致某些内存访问的页表走行。
kperf收集TLB效率
# /home/qatest/script/kperf/kperf --tlb --machine kunpeng920b /root/code/DynamoRIO-AArch64-Linux-11.3.0-1/bin64/drrun   -t drmemtrace -tool miss_analyzer -LL_miss_file rec.csv -- /root/code/dynamorio/clients/drcachesim/tests/stride_benchmark
architecture kunpeng920b
=========== TLB Performance Metrics ===========
------------------ missrate -------------------
l1i_tlb_missrate:                        0.24 %
l1d_tlb_missrate:                        0.26 %
l2i_tlb_missrate:                        0.16 %
l2d_tlb_missrate:                        0.76 %
------------------ walk rate ------------------
itlb_walk_rate:                        0.05 %
dtlb_walk_rate:                        0.00 %
-------------------- mpki ---------------------
l1i_tlb_mpki:                              0.69
l1d_tlb_mpki:                              1.09
l2i_tlb_mpki:                              0.00
l2d_tlb_mpki:                              0.01
itlb_walk_mpki:                            0.13
dtlb_walk_mpki:                            0.01

[*]工作负载特征总结
下图展示了所有MPKI和miss率的总结在一个图表上。


从特性数据来看,该工作负载严重受限于后端,后端停滞率高达 83%。该工作负载在数据缓存方面显示出显著的后端压力,L1D MPKI 为 106,L2 MPKI 为 78,最后一级缓存读取 MPKI 为 195。CPU 前端运行平稳,分支 MPKI、L1I MPKI 和 ITLB MPKI 等统计指标几乎可以忽略不计,这与前端零停滞相对应。表征证据支持工作负载行为(如测试源代码备注所述),即我们的系统存在内存瓶颈,下一步我们应研究如何解决这一问题。

[*]热点分析事件
为找出代码执行瓶颈以优化代码,我们应进一步深入研究两个方面: 后端停滞和分层 D-Cache 事件。为进一步调查,除 INST_RETIRED 和 CPU_CYCLES 外,热点分析的简短事件列表包括
L1D_CACHE、L1D_CACHE_REFILL、L2D_CACHE、L2D_CACHE_REFILL、LL_CACHE_RD、LL_CACHE_MISS_RD。
参考资料


[*]软件测试精品书籍文档下载持续更新 https://github.com/china-testing/python-testing-examples 请点赞,谢谢!
[*]本文涉及的python测试开发库 谢谢点赞! https://github.com/china-testing/python_cn_resouce
[*]python精品书籍下载 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
[*]Linux精品书籍下载 https://www.cnblogs.com/testing-/p/17438558.html
5.1.2 第 2 阶段:使用 Perf 事件采样模式进行热点分析

我们收集了所有简短列出的热点分析事件的 perf 采样数据,并研究了注释反汇编代码。

[*]Stride 基准源代码
#include #include #include #define MEM_BARRIER() __asm__ __volatile__("" ::: "memory")intmain(int argc, const char *argv[]){    // Cache line size in bytes.    const int kLineSize = 64;    // Number of cache lines skipped by the stream every iteration.    const int kStride = 7;    // Number of 1-byte elements in the array.    const size_t kArraySize = 16 * 1024 * 1024;    // Number of iterations in the main loop.    const int kIterations = 20000;    // The main vector/array used for emulating pointer chasing.    unsigned char *buffer = new unsigned char;    memset(buffer, kStride, kArraySize);    // Add a memory barrier so the call doesn't get optimized away or    // reordered with respect to callers.    MEM_BARRIER();    int position = 0;    // Here the code will pointer chase through the array skipping forward    // kStride cache lines at a time. Since kStride is an odd number, the main    // loop will touch different cache lines as it wraps around.    for (int loop = 0; loop < kIterations; ++loop) {      // This prefetching instruction results in a speedup of >2x      // on a Skylake machine running Linux when compiled with g++ -O3.      // const int prefetch_distance = 5 * kStride * kLineSize;      // __builtin_prefetch(&buffer, 0, 0);      position += (buffer * kLineSize);      position &= (kArraySize - 1);    }    // Add a memory barrier so the call doesn't get optimized away or    // reordered with respect to callers.    MEM_BARRIER();    std::cerr
页: [1]
查看完整版本: Arm Neoverse N1 Core: 性能分析方法