1. 引言
性能分析是优化GPU应用程序的关键环节。ROCm通过hsakmt层提供了完整的性能计数器pmc(Performance Counter)管理机制,允许开发者监控GPU硬件的运行状态,收集性能数据,从而定位性能瓶颈。
本文档作为系列文章的第一篇,将介绍ROCm rocr里的性能跟踪与分析的整体架构、核心概念和典型使用流程。
2. 架构概览
2.1 系统层次结构
ROCm性能跟踪系统采用分层架构:
+-----------------------------------+
| 用户应用层 (User Application) |
| - ROCProfiler |
| - 自定义分析工具 |
+-----------------------------------+
↓ HSA API
+-----------------------------------+
| HSA KMT API Layer |
| - hsaKmtPmcGetCounterProperties |
| - hsaKmtPmcRegisterTrace |
| - hsaKmtPmcStartTrace |
| - hsaKmtPmcQueryTrace |
| - hsaKmtPmcStopTrace |
+-----------------------------------+
↓ ioctl
+-----------------------------------+
| KFD Driver (Kernel) |
| - 性能计数器硬件管理 |
| - perf_event 子系统集成 |
+-----------------------------------+
↓ 硬件接口
+-----------------------------------+
| GPU 硬件 |
| - 各种性能计数器块 |
| (SQ, TCC, TCP, etc.) |
+-----------------------------------+
2.2 核心组件
2.2.1 性能计数器块(Performance Counter Block)
GPU硬件包含多个功能模块,每个模块都有自己的性能计数器:
- CB (Color Buffer): 颜色缓冲区操作计数器
- DB (Depth Buffer): 深度缓冲区操作计数器
- SQ (Shader Sequencer): 着色器执行计数器
- TCC (Texture Cache per Channel): 纹理缓存计数器
- TCP (Texture Cache per Pipe): 纹理缓存管道计数器
- MC (Memory Controller): 内存控制器计数器
- GRBM (Graphics Register Bus Manager): 图形寄存器总线管理器计数器
- 等20多种不同的硬件块
每个块包含:
- 多个计数器(Counters): 监控特定硬件事件
- 并发槽位限制(Concurrent Slots): 同时激活的计数器数量上限
2.2.2 计数器属性
每个性能计数器具有以下属性:
struct HsaCounter {
HSAuint32 BlockIndex; // 所属硬件块ID
HSAuint64 CounterId; // 计数器ID
HSAuint32 CounterSizeInBits; // 计数器位宽
HSAuint64 CounterMask; // 计数器掩码
HsaCounterFlags Flags; // 标志位
HSA_PROFILE_TYPE Type; // 计数器类型
};
2.2.3 跟踪会话(Trace Session)
跟踪会话是性能分析的基本单位,由以下元素组成:
- TraceId: 唯一标识一个跟踪会话
- GPU Node: 目标GPU节点
- Counter Set: 要监控的计数器集合
- Trace Buffer: 存储采样数据的缓冲区
- State: 会话状态(STOPPED/STARTED)
3. 核心数据结构
3.1 perf_trace 结构
struct perf_trace {
uint32_t magic4cc; // 魔数,用于验证 (0x54415348 = "HSAT")
uint32_t gpu_id; // GPU设备ID
enum perf_trace_state state; // 跟踪状态
uint32_t num_blocks; // 涉及的硬件块数量
void *buf; // 数据缓冲区指针
uint64_t buf_size; // 缓冲区大小
struct perf_trace_block blocks[0]; // 可变长度数组,存储各块信息
};
3.2 perf_trace_block 结构
struct perf_trace_block {
enum perf_block_id block_id; // 硬件块ID
uint32_t num_counters; // 该块中的计数器数量
uint64_t *counter_id; // 计数器ID数组
int *perf_event_fd; // perf事件文件描述符数组
};
3.3 内存布局
注册跟踪时,系统分配一块连续内存,布局如下:
+-----------------------------------+ ← trace
| perf_trace 结构体 |
+-----------------------------------+ ← trace->blocks[0]
| perf_trace_block 0 |
| perf_trace_block 1 |
| ... |
| perf_trace_block N-1 |
+-----------------------------------+ ← counter_id_ptr
| block 0's counter IDs (uint64) |
| block 1's counter IDs |
| ... |
| block N-1's counter IDs |
+-----------------------------------+ ← perf_event_fd
| block 0's perf_event_fds (int) |
| block 1's perf_event_fds |
| ... |
| block N-1's perf_event_fds |
+-----------------------------------+
4. 典型使用流程
4.1 完整流程图
开始
↓
[1] hsaKmtPmcGetCounterProperties()
↓ 查询GPU支持的性能计数器
| 返回: HsaCounterProperties (包含所有块和计数器信息)
↓
[2] 应用层选择要监控的计数器
↓
[3] hsaKmtPmcRegisterTrace()
↓ 注册计数器集合
| 验证: 计数器数量 ≤ 并发限制
| 分配: perf_trace 结构
| 返回: TraceId
↓
[4] hsaKmtPmcAcquireTraceAccess()
↓ 获取跟踪访问权限
| 验证: TraceId 有效性
↓
[5] 分配跟踪缓冲区 (TraceBuffer)
↓
[6] hsaKmtPmcStartTrace()
↓ 启动性能跟踪
| 操作: ioctl(PERF_EVENT_IOC_ENABLE)
| 状态: STOPPED → STARTED
↓
[7] 执行被测GPU工作负载
↓
[8] hsaKmtPmcQueryTrace() (可多次调用)
↓ 查询计数器当前值
| 读取: perf_event_fd 数据
| 填充: TraceBuffer
↓
[9] hsaKmtPmcStopTrace()
↓ 停止性能跟踪
| 操作: ioctl(PERF_EVENT_IOC_DISABLE)
| 状态: STARTED → STOPPED
↓
[10] 分析 TraceBuffer 中的数据
↓
[11] hsaKmtPmcReleaseTraceAccess()
↓ 释放跟踪访问权限
↓
[12] hsaKmtPmcUnregisterTrace()
↓ 注销跟踪会话
| 释放: perf_trace 结构
↓
结束
4.2 流程详细说明
阶段一:准备阶段
步骤1: 查询计数器属性
- 调用
hsaKmtPmcGetCounterProperties(NodeId, &CounterProperties) - 获取GPU节点支持的所有性能计数器信息
- 系统会缓存结果,后续调用直接返回缓存
步骤2: 选择计数器
- 应用层根据性能分析需求选择计数器
- 构造
HsaCounter数组
步骤3: 注册跟踪
- 调用
hsaKmtPmcRegisterTrace(NodeId, NumCounters, Counters, &TraceRoot) - 系统验证计数器合法性和并发限制
- 分配并初始化
perf_trace结构 - 返回
TraceId和最小缓冲区大小
步骤4: 获取访问权限
- 调用
hsaKmtPmcAcquireTraceAccess(NodeId, TraceId) - 验证 TraceId 有效性
阶段二:跟踪阶段
步骤5: 分配缓冲区
- 根据
TraceBufferMinSizeBytes分配内存 - 通常使用页对齐的大小
步骤6: 启动跟踪
- 调用
hsaKmtPmcStartTrace(TraceId, TraceBuffer, BufferSize) - 系统通过 ioctl 启用所有 perf 事件
- 跟踪状态变为 STARTED
步骤7: 执行工作负载
- 运行需要性能分析的GPU代码
- GPU执行过程中,硬件计数器持续累加
步骤8: 查询计数器
- 调用
hsaKmtPmcQueryTrace(TraceId)读取当前值 - 可以多次调用以获取时间序列数据
- 数据写入 TraceBuffer
阶段三:清理阶段
步骤9: 停止跟踪
- 调用
hsaKmtPmcStopTrace(TraceId) - 系统通过 ioctl 禁用所有 perf 事件
- 跟踪状态变为 STOPPED
步骤10: 分析数据
- 解析 TraceBuffer 中的性能数据
- 计算性能指标
步骤11-12: 释放资源
- 调用
hsaKmtPmcReleaseTraceAccess(NodeId, TraceId) - 调用
hsaKmtPmcUnregisterTrace(NodeId, TraceId) - 释放 perf_trace 结构和其他资源
5. 关键设计特点
5.1 并发限制验证
每个硬件块都有并发槽位限制,例如:
- SQ块可能支持16个并发计数器
- TCC块可能支持4个并发计数器
系统在注册时会验证:
if (num_counters[block_id] > concurrent_limit) {
return HSAKMT_STATUS_INVALID_PARAMETER;
}
5.2 缓存机制
首次调用 hsaKmtPmcGetCounterProperties() 时,系统查询硬件并缓存结果:
if (counter_props[NodeId]) {
*CounterProperties = counter_props[NodeId]; // 直接返回缓存
return HSAKMT_STATUS_SUCCESS;
}
5.3 状态管理
跟踪会话有明确的状态转换:
STOPPED ←→ STARTED
↑ ↓
[Register] [Start]
↑ ↓
[Stop] [Query]
5.4 内存管理策略
- 单次分配:
perf_trace及其所有子结构在一次calloc中分配 - 连续布局: 所有数据紧密排列,提高缓存友好性
- 自动清理: Unregister 时自动停止正在运行的跟踪
5.5 错误处理
系统提供多层错误检查:
- 魔数验证: 通过
magic4cc验证 TraceId 有效性 - 节点验证: 确保 NodeId 和 gpu_id 匹配
- 状态验证: 确保操作在正确的状态下进行
- 回滚机制: 部分失败时自动回滚(如 StartTrace 失败)
6. Linux perf_event 集成
ROCm性能跟踪基于Linux的 perf_event 子系统:
// 每个计数器对应一个 perf_event_fd
int perf_event_fd;
// 启用计数器
ioctl(perf_event_fd, PERF_EVENT_IOC_ENABLE, NULL);
// 读取计数值
struct perf_counts_values {
uint64_t val; // 计数值
uint64_t ena; // 启用时间
uint64_t run; // 运行时间
};
read(perf_event_fd, &content, sizeof(content));
// 禁用计数器
ioctl(perf_event_fd, PERF_EVENT_IOC_DISABLE, NULL);
7. 使用场景
7.1 基准测试(Benchmarking)
监控关键性能指标:
- 内存带宽利用率(通过TCC计数器)
- 计算单元利用率(通过SQ计数器)
- 缓存命中率(通过TCP/TCC计数器)
7.2 性能优化
识别瓶颈:
- 内存瓶颈:高L2缓存未命中率
- 计算瓶颈:高ALU利用率,低内存访问
- 同步瓶颈:波前停顿时间长
7.3 功耗分析
监控功耗相关事件:
- GPU时钟频率
- 活跃CU数量
- 内存控制器活动
8. 限制与注意事项
8.1 硬件限制
- 并发计数器数量: 每个块有固定的并发槽位限制
- 采样粒度: 计数器通常按波前或时钟周期累加
- 精度: 硬件计数器可能存在微小误差
8.2 软件限制
- 单进程: 一个进程独占GPU性能计数器
- 权限: 某些计数器可能需要特权访问
- 开销: 频繁查询会引入性能开销
8.3 最佳实践
- 最小化计数器数量: 只监控必要的计数器
- 批量查询: 减少
QueryTrace调用频率 - 预分配缓冲区: 使用足够大的 TraceBuffer
- 错误处理: 始终检查返回值
- 资源清理: 确保调用 Unregister 释放资源
9. 示例代码框架
// 1. 查询计数器属性
HsaCounterProperties *props;
hsaKmtPmcGetCounterProperties(nodeId, &props);
// 2. 选择计数器(示例:选择SQ块的2个计数器)
HsaCounter counters[2];
counters[0].BlockIndex = PERFCOUNTER_BLOCKID__SQ;
counters[0].CounterId = 0x1; // SQ计数器1
counters[1].BlockIndex = PERFCOUNTER_BLOCKID__SQ;
counters[1].CounterId = 0x2; // SQ计数器2
// 3. 注册跟踪
HsaPmcTraceRoot traceRoot;
hsaKmtPmcRegisterTrace(nodeId, 2, counters, &traceRoot);
// 4. 获取访问权限
hsaKmtPmcAcquireTraceAccess(nodeId, traceRoot.TraceId);
// 5. 分配缓冲区
void *buffer = malloc(traceRoot.TraceBufferMinSizeBytes);
// 6. 启动跟踪
hsaKmtPmcStartTrace(traceRoot.TraceId, buffer, traceRoot.TraceBufferMinSizeBytes);
// 7. 执行GPU工作负载
// ... launch kernels ...
// 8. 查询计数器
hsaKmtPmcQueryTrace(traceRoot.TraceId);
// 9. 分析数据
uint64_t *data = (uint64_t *)buffer;
printf("Counter 0: %lu\n", data[0]);
printf("Counter 1: %lu\n", data[1]);
// 10. 停止跟踪
hsaKmtPmcStopTrace(traceRoot.TraceId);
// 11. 释放访问权限
hsaKmtPmcReleaseTraceAccess(nodeId, traceRoot.TraceId);
// 12. 注销跟踪
hsaKmtPmcUnregisterTrace(nodeId, traceRoot.TraceId);
// 13. 清理
free(buffer);
10. 总结
ROCm性能跟踪与分析系统提供了强大而灵活的GPU性能监控能力:
- 完整的硬件覆盖: 支持20+种硬件块的性能计数器
- 标准化接口: 基于HSA标准API
- Linux集成: 充分利用perf_event子系统
- 易于使用: 清晰的生命周期管理
在接下来的系列文章中,我们将深入探讨:
- 第二篇: 性能计数器查询与计数器类型详解
- 第三篇: 跟踪会话注册与资源管理
- 第四篇: 跟踪启动、数据采集与停止
- 第五篇: 数据解析与性能分析方法
- 第六篇: 高级应用与优化技巧
810

被折叠的 条评论
为什么被折叠?



