1. 引言
在前两篇文章中,我们了解了ROCm性能跟踪系统的整体架构和计数器查询机制。本文将深入探讨 hsaKmtPmcRegisterTrace() 函数的实现,这是性能跟踪流程中最复杂也最关键的一步。
跟踪注册负责:
- 验证计数器选择的合法性
- 检查并发限制约束
- 分配和组织跟踪资源
- 建立计数器到硬件块的映射
- 返回跟踪会话标识符
2. API 概览
2.1 函数签名
HSAKMT_STATUS HSAKMTAPI hsaKmtPmcRegisterTrace(
HSAuint32 NodeId, // 输入:GPU节点ID
HSAuint32 NumberOfCounters, // 输入:计数器数量
HsaCounter *Counters, // 输入:计数器数组
HsaPmcTraceRoot *TraceRoot // 输出:跟踪根信息
);
2.2 输入参数详解
NodeId
- GPU节点标识符
- 用于定位目标GPU设备
- 必须是有效的节点ID
NumberOfCounters
- 要监控的计数器数量
- 必须 > 0
- 受硬件并发限制约束
- 当前实现限制最大512个
Counters
- 计数器描述数组
- 每个元素指定一个性能计数器
- 包含块索引、计数器ID、类型等信息
2.3 输出参数详解
TraceRoot
typedef struct {
HSAuint32 NumberOfPasses; // 采样轮数(通常为1)
HSAuint64 TraceBufferMinSizeBytes; // 最小缓冲区大小
HSATraceId TraceId; // 跟踪会话标识符
} HsaPmcTraceRoot;
- NumberOfPasses: 当前实现始终为1
- TraceBufferMinSizeBytes: 计算得出的最小缓冲区大小(页对齐)
- TraceId: 指向
perf_trace结构的64位句柄
2.4 返回值
| 返回值 | 说明 |
|---|---|
HSAKMT_STATUS_SUCCESS | 注册成功 |
HSAKMT_STATUS_NO_MEMORY | 内存不足 |
HSAKMT_STATUS_INVALID_PARAMETER | 参数无效 |
HSAKMT_STATUS_INVALID_NODE_UNIT | NodeId无效 |
3. 实现流程详解
3.1 整体流程图
hsaKmtPmcRegisterTrace()
↓
[1] 分配临时counter_id二维数组 (MAX_BLOCKS × MAX_COUNTERS)
↓
[2] 初始化统计变量
├─ num_counters[BLOCKID] = 0
├─ num_blocks = 0
├─ total_counters = 0
└─ min_buf_size = 0
↓
[3] 参数验证
├─ counter_props 是否初始化?
├─ Counters/TraceRoot 是否为NULL?
├─ NumberOfCounters > 0?
├─ NodeId 是否有效?
└─ NumberOfCounters ≤ MAX_COUNTERS?
↓
[4] 第一轮遍历:统计和验证
FOR each counter in Counters:
├─ 验证 BlockIndex < MAX
├─ 跳过非特权计数器
├─ 累加 min_buf_size
├─ 检查块内计数器数量 < MAX_COUNTERS
├─ 记录 counter_id[BlockIndex][j]
├─ num_counters[BlockIndex]++
└─ total_counters++
↓
[5] 第二轮遍历:验证并发限制
FOR each block with counters:
├─ 获取 concurrent_limit
├─ 验证 num_counters[block] ≤ concurrent_limit
└─ num_blocks++
↓
[6] 分配 perf_trace 结构(单次分配)
Size = sizeof(perf_trace)
+ sizeof(perf_trace_block) × num_blocks
+ sizeof(uint64_t) × total_counters
+ sizeof(int) × total_counters
↓
[7] 初始化内存布局指针
├─ counter_id_ptr → 计数器ID区域
└─ fd_ptr → perf_event_fd区域
↓
[8] 第三轮遍历:填充块信息
FOR each block with counters:
├─ 设置 blocks[i].counter_id
├─ 复制 counter_id 数组
├─ 设置 blocks[i].perf_event_fd
├─ 设置 num_counters
├─ 设置 block_id
└─ 移动指针
↓
[9] 设置 perf_trace 元数据
├─ magic4cc = 0x54415348
├─ gpu_id
├─ state = STOPPED
└─ num_blocks
↓
[10] 填充 TraceRoot
├─ NumberOfPasses = 1
├─ TraceBufferMinSizeBytes = PAGE_ALIGN_UP(min_buf_size)
└─ TraceId = (uint64_t)trace
↓
[11] 释放临时 counter_id 数组
↓
返回 SUCCESS
3.2 关键常量定义
#define MAX_COUNTERS 512 // 最大计数器数量限制
#define PERFCOUNTER_BLOCKID__MAX 23 // 硬件块类型数量
#define HSA_PERF_MAGIC4CC 0x54415348 // 魔数 "HSAT"
4. 源码深度分析
4.1 初始化阶段
HSAKMT_STATUS HSAKMTAPI hsaKmtPmcRegisterTrace(
HSAuint32 NodeId,
HSAuint32 NumberOfCounters,
HsaCounter *Counters,
HsaPmcTraceRoot *TraceRoot)
{
uint32_t gpu_id, i, j;
uint64_t min_buf_size = 0;
struct perf_trace *trace = NULL;
uint32_t concurrent_limit;
const uint32_t MAX_COUNTERS = 512;
// 分配临时二维数组,用于按块组织计数器ID
// [块ID][计数器索引] → 计数器ID
uint64_t *counter_id = malloc(
PERFCOUNTER_BLOCKID__MAX * MAX_COUNTERS * sizeof(uint64_t));
uint32_t num_counters[PERFCOUNTER_BLOCKID__MAX] = {0}; // 每块的计数器数
uint32_t block, num_blocks = 0, total_counters = 0;
uint64_t *counter_id_ptr;
int *fd_ptr;
pr_debug("[%s] Number of counters %d\n", __func__, NumberOfCounters);
临时数组的作用:
counter_id[23][512]: 按块分组存储计数器ID- 后续用于填充
perf_trace_block结构 - 注册完成后释放
内存计算:
临时数组大小 = 23 × 512 × 8 字节 = 94,208 字节 ≈ 92 KB
4.2 参数验证阶段
// 检查内存分配
if (counter_id == NULL) {
pr_err("Failed to allocate memory for counter_id. "
"Requested %zu bytes.\n",
PERFCOUNTER_BLOCKID__MAX * MAX_COUNTERS * sizeof(uint64_t));
return HSAKMT_STATUS_NO_MEMORY;
}
// 检查计数器属性系统是否初始化
if (!counter_props) {
pr_err("Profiling is not available, counter_props is NULL.\n");
goto no_memory_exit;
}
// 检查输入参数
if (!Counters || !TraceRoot || NumberOfCounters == 0)
goto invalid_parameter_exit;
// 验证并转换节点ID
if (hsakmt_validate_nodeid(&hsakmt_primary_kfd_ctx, NodeId, &gpu_id)
!= HSAKMT_STATUS_SUCCESS) {
free(counter_id);
return HSAKMT_STATUS_INVALID_NODE_UNIT;
}
// 检查计数器数量上限
if (NumberOfCounters > MAX_COUNTERS) {
pr_err("MAX_COUNTERS is too small for %d.\n", NumberOfCounters);
goto no_memory_exit;
}
多层验证:
- 内存分配成功性
- 全局状态有效性
- 参数完整性
- 节点ID有效性
- 数量上限
4.3 第一轮遍历:统计和分组
// 计算最小缓冲区大小并按块组织计数器
for (i = 0; i < NumberOfCounters; i++) {
// 验证块索引
if (Counters[i].BlockIndex >= PERFCOUNTER_BLOCKID__MAX)
goto invalid_parameter_exit;
// 只有特权计数器需要注册(实际上所有类型都注册)
if (Counters[i].Type > HSA_PROFILE_TYPE_PRIVILEGED_STREAMING)
continue;
// 累加缓冲区大小需求
min_buf_size += Counters[i].CounterSizeInBits / BITS_PER_BYTE;
// 获取该块当前的计数器数量(作为索引)
j = num_counters[Counters[i].BlockIndex];
// 防止单个块的计数器数超过MAX_COUNTERS
if (j >= MAX_COUNTERS) {
pr_err("Counter ID exceeded MAX_COUNTERS for block %d.\n",
Counters[i].BlockIndex);
goto invalid_parameter_exit;
}
// 存储计数器ID到二维数组
// counter_id[块ID][块内索引] = 计数器ID
counter_id[Counters[i].BlockIndex * MAX_COUNTERS + j] =
Counters[i].CounterId;
// 更新该块的计数器数量
num_counters[Counters[i].BlockIndex]++;
// 更新总计数器数
total_counters++;
}
关键数据结构转换:
输入(线性数组):
Counters[0] = {BlockIndex=13, CounterId=0x1, ...} // SQ块
Counters[1] = {BlockIndex=13, CounterId=0x2, ...} // SQ块
Counters[2] = {BlockIndex=17, CounterId=0x5, ...} // TCC块
Counters[3] = {BlockIndex=13, CounterId=0x3, ...} // SQ块
输出(按块分组):
counter_id[13][0] = 0x1 // SQ块的第0个计数器
counter_id[13][1] = 0x2 // SQ块的第1个计数器
counter_id[13][2] = 0x3 // SQ块的第2个计数器
counter_id[17][0] = 0x5 // TCC块的第0个计数器
num_counters[13] = 3 // SQ块有3个计数器
num_counters[17] = 1 // TCC块有1个计数器
缓冲区大小计算:
// 假设选择了4个64位计数器
min_buf_size = 4 × 64 / 8 = 32 字节
4.4 第二轮遍历:并发限制验证
// 验证每个块的计数器数量不超过并发限制
for (i = 0; i < PERFCOUNTER_BLOCKID__MAX; i++) {
// 跳过没有选择计数器的块
if (!num_counters[i])
continue;
// 获取该块的并发槽位限制
concurrent_limit = get_block_concurrent_limit(NodeId, i);
// 如果返回0,说明块不存在
if (!concurrent_limit) {
pr_err("Invalid block ID: %d\n", i);
goto invalid_parameter_exit;
}
// 检查是否超过并发限制
if (num_counters[i] > concurrent_limit) {
pr_err("Counters exceed the limit.\n");
goto invalid_parameter_exit;
}
// 统计涉及的块数量
num_blocks++;
}
// 至少需要一个块
if (!num_blocks)
goto invalid_parameter_exit;
并发限制检查示例:
假设SQ块的并发限制是16:
num_counters[PERFCOUNTER_BLOCKID__SQ] = 20 // 选择了20个SQ计数器
concurrent_limit = 16 // SQ块限制16个
// 检查失败,返回错误
if (20 > 16) {
pr_err("Counters exceed the limit.\n");
goto invalid_parameter_exit;
}
4.5 内存分配阶段
// 分配perf_trace结构及其所有子结构(单次分配)
trace = (struct perf_trace *)calloc(
sizeof(struct perf_trace) // 基础结构
+ sizeof(struct perf_trace_block) * num_blocks // 块数组
+ sizeof(uint64_t) * total_counters // 计数器ID数组
+ sizeof(int) * total_counters, // perf_event_fd数组
1);
if (!trace) {
pr_err("Failed to allocate memory for trace. Requested %zu bytes.\n",
sizeof(struct perf_trace)
+ sizeof(struct perf_trace_block) * num_blocks
+ sizeof(uint64_t) * total_counters
+ sizeof(int) * total_counters);
goto no_memory_exit;
}
内存布局可视化:
假设选择了2个块(SQ: 3个计数器,TCC: 1个计数器):
地址 内容 大小
───────────────────────────────────────────────────────────
0x1000 perf_trace 48 字节
├─ magic4cc: 0x54415348
├─ gpu_id: 1
├─ state: STOPPED (0)
├─ num_blocks: 2
├─ buf: NULL
└─ buf_size: 0
───────────────────────────────────────────────────────────
0x1030 perf_trace_block[0] (SQ块) 32 字节
├─ block_id: 13
├─ num_counters: 3
├─ counter_id: → 0x1070
└─ perf_event_fd: → 0x1090
───────────────────────────────────────────────────────────
0x1050 perf_trace_block[1] (TCC块) 32 字节
├─ block_id: 17
├─ num_counters: 1
├─ counter_id: → 0x1088
└─ perf_event_fd: → 0x109C
───────────────────────────────────────────────────────────
0x1070 counter_id[0][0-2] (SQ的3个ID) 24 字节
├─ [0] = 0x1
├─ [1] = 0x2
└─ [2] = 0x3
───────────────────────────────────────────────────────────
0x1088 counter_id[1][0] (TCC的1个ID) 8 字节
└─ [0] = 0x5
───────────────────────────────────────────────────────────
0x1090 perf_event_fd[0][0-2] (SQ的3个fd) 12 字节
├─ [0] = -1 (未初始化)
├─ [1] = -1
└─ [2] = -1
───────────────────────────────────────────────────────────
0x109C perf_event_fd[1][0] (TCC的1个fd) 4 字节
└─ [0] = -1
───────────────────────────────────────────────────────────
总大小:约 156 字节
实际大小计算:
total_size = 48 // perf_trace
+ 32 × 2 // 2个perf_trace_block
+ 8 × 4 // 4个uint64_t计数器ID
+ 4 × 4 // 4个int文件描述符
= 48 + 64 + 32 + 16
= 160 字节
4.6 初始化指针阶段
/* Allocated area is partitioned as:
* +---------------------------------+ trace
* | perf_trace |
* |---------------------------------| trace->blocks[0]
* | perf_trace_block 0 |
* | .... |
* | perf_trace_block N-1 | trace->blocks[N-1]
* |---------------------------------| <-- counter_id_ptr starts here
* | block 0's counter IDs(uint64_t) |
* | ...... |
* | block N-1's counter IDs |
* |---------------------------------| <-- perf_event_fd starts here
* | block 0's perf_event_fds(int) |
* | ...... |
* | block N-1's perf_event_fds |
* +---------------------------------+
*/
block = 0;
// 计算counter_id数组的起始位置
counter_id_ptr = (uint64_t *)((char *)trace
+ sizeof(struct perf_trace)
+ sizeof(struct perf_trace_block) * num_blocks);
// 计算perf_event_fd数组的起始位置
fd_ptr = (int *)(counter_id_ptr + total_counters);
指针计算示例:
// 假设 trace = 0x1000
trace = 0x1000
// counter_id_ptr 计算
counter_id_ptr = (uint64_t *)((char *)0x1000
+ 48 // sizeof(perf_trace)
+ 32 * 2) // 2个block
= 0x1070
// fd_ptr 计算
fd_ptr = (int *)(0x1070 + 4 * 8) // 跳过4个uint64_t
= 0x1090
4.7 第三轮遍历:填充块信息
// 填充每个块的信息
for (i = 0; i < PERFCOUNTER_BLOCKID__MAX; i++) {
// 跳过没有计数器的块
if (!num_counters[i])
continue;
// 设置该块的counter_id数组指针
trace->blocks[block].counter_id = counter_id_ptr;
// 复制计数器ID到连续内存
for (j = 0; j < num_counters[i]; j++)
trace->blocks[block].counter_id[j] =
counter_id[i * MAX_COUNTERS + j];
// 设置perf_event_fd数组指针
trace->blocks[block].perf_event_fd = fd_ptr;
// 设置计数器数量
trace->blocks[block].num_counters = num_counters[i];
// 设置块ID
trace->blocks[block].block_id = i;
// 移动到下一个块
block++;
// 移动指针到下一个块的存储区域
counter_id_ptr += num_counters[i];
fd_ptr += num_counters[i];
}
数据流转换:
从临时二维数组:
counter_id[13 * 512 + 0] = 0x1
counter_id[13 * 512 + 1] = 0x2
counter_id[13 * 512 + 2] = 0x3
counter_id[17 * 512 + 0] = 0x5
到连续内存:
trace->blocks[0].counter_id → [0x1, 0x2, 0x3]
trace->blocks[1].counter_id → [0x5]
4.8 设置元数据和返回
// 设置perf_trace的元数据
trace->magic4cc = HSA_PERF_MAGIC4CC; // 0x54415348 = "HSAT"
trace->gpu_id = gpu_id;
trace->state = PERF_TRACE_STATE__STOPPED;
trace->num_blocks = num_blocks;
// 填充返回给调用者的信息
TraceRoot->NumberOfPasses = 1;
TraceRoot->TraceBufferMinSizeBytes = PAGE_ALIGN_UP(min_buf_size);
TraceRoot->TraceId = PORT_VPTR_TO_UINT64(trace);
// 释放临时二维数组
free(counter_id);
return HSAKMT_STATUS_SUCCESS;
no_memory_exit:
free(counter_id);
return HSAKMT_STATUS_NO_MEMORY;
invalid_parameter_exit:
free(counter_id);
return HSAKMT_STATUS_INVALID_PARAMETER;
}
PAGE_ALIGN_UP 宏:
#define PAGE_SIZE 4096
#define PAGE_ALIGN_UP(x) (((x) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))
// 示例
min_buf_size = 32
PAGE_ALIGN_UP(32) = ((32 + 4095) & ~4095)
= (4127 & 0xFFFFF000)
= 4096 (1页)
min_buf_size = 5000
PAGE_ALIGN_UP(5000) = ((5000 + 4095) & ~4095)
= (9095 & 0xFFFFF000)
= 8192 (2页)
5. 错误处理机制
5.1 错误路径分析
// 内存不足错误
no_memory_exit:
free(counter_id); // 清理临时数组
return HSAKMT_STATUS_NO_MEMORY;
// 参数无效错误
invalid_parameter_exit:
free(counter_id); // 清理临时数组
return HSAKMT_STATUS_INVALID_PARAMETER;
5.2 常见错误场景
场景1:计数器数量超限
// 用户选择了太多计数器
NumberOfCounters = 600 // 超过MAX_COUNTERS(512)
// 检查失败
if (NumberOfCounters > MAX_COUNTERS) {
pr_err("MAX_COUNTERS is too small for %d.\n", NumberOfCounters);
goto no_memory_exit;
}
场景2:单块计数器超限
// 用户为SQ块选择了20个计数器,但SQ只支持16个并发
num_counters[PERFCOUNTER_BLOCKID__SQ] = 20
concurrent_limit = 16
// 检查失败
if (20 > 16) {
pr_err("Counters exceed the limit.\n");
goto invalid_parameter_exit;
}
场景3:无效的块索引
// 用户提供了无效的块索引
Counters[i].BlockIndex = 100 // 超过PERFCOUNTER_BLOCKID__MAX(23)
// 检查失败
if (Counters[i].BlockIndex >= PERFCOUNTER_BLOCKID__MAX)
goto invalid_parameter_exit;
场景4:内存分配失败
// 系统内存不足
trace = calloc(...);
if (!trace) {
pr_err("Failed to allocate memory for trace. "
"Requested %zu bytes.\n", ...);
goto no_memory_exit;
}
6. TraceId 的生命周期
6.1 TraceId 的本质
// TraceId 实际上是指向 perf_trace 结构的指针
TraceRoot->TraceId = PORT_VPTR_TO_UINT64(trace);
// PORT_VPTR_TO_UINT64 宏定义
#define PORT_VPTR_TO_UINT64(vptr) ((HSAuint64)(uintptr_t)(vptr))
// 逆向转换
#define PORT_UINT64_TO_VPTR(id) ((void *)(uintptr_t)(id))
6.2 生命周期管理
注册阶段 (Register)
↓
创建 perf_trace 结构
↓
返回 TraceId (指针)
↓
应用程序持有 TraceId
↓
[使用 TraceId 的API调用]
├─ Acquire
├─ Start
├─ Query
├─ Stop
└─ Release
↓
注销阶段 (Unregister)
↓
释放 perf_trace 结构
↓
TraceId 失效
6.3 TraceId 验证
所有使用TraceId的API都会进行验证:
// 示例:hsaKmtPmcStopTrace()
HSAKMT_STATUS HSAKMTAPI hsaKmtPmcStopTrace(HSATraceId TraceId)
{
struct perf_trace *trace;
// 检查TraceId非零
if (TraceId == 0)
return HSAKMT_STATUS_INVALID_PARAMETER;
// 转换为指针
trace = (struct perf_trace *)PORT_UINT64_TO_VPTR(TraceId);
// 验证魔数
if (trace->magic4cc != HSA_PERF_MAGIC4CC)
return HSAKMT_STATUS_INVALID_HANDLE;
// 后续操作...
}
7. 资源管理策略
7.1 单次分配策略
优势:
- 简化管理: 只需一次malloc/free
- 缓存友好: 数据紧密排列,减少缓存未命中
- 原子性: 分配失败时无需复杂的清理逻辑
实现:
// 一次性分配所有内存
void *memory = calloc(total_size, 1);
// 手动分区
perf_trace *trace = (perf_trace *)memory;
perf_trace_block *blocks = (perf_trace_block *)(trace + 1);
uint64_t *counter_ids = (uint64_t *)(blocks + num_blocks);
int *fds = (int *)(counter_ids + total_counters);
7.2 内存所有权
创建时 注册时 使用时 注销时
──────── ──────── ──────── ────────
malloc → 返回TraceId → 应用持有 → free
(转移所有权) (只读访问) (回收所有权)
7.3 清理机制
// hsaKmtPmcUnregisterTrace()
HSAKMT_STATUS HSAKMTAPI hsaKmtPmcUnregisterTrace(
HSAuint32 NodeId,
HSATraceId TraceId)
{
// ... 验证代码 ...
// 如果跟踪正在运行,先停止
if (trace->state == PERF_TRACE_STATE__STARTED) {
HSAKMT_STATUS status = hsaKmtPmcStopTrace(TraceId);
if (status != HSAKMT_STATUS_SUCCESS)
return status;
}
// 释放整个perf_trace结构
// 由于是单次分配,一次free即可释放所有资源
free(trace);
return HSAKMT_STATUS_SUCCESS;
}
8. 实战示例
8.1 基本注册流程
#include <stdio.h>
#include <hsakmt.h>
int register_simple_trace(HSAuint32 nodeId)
{
HSAKMT_STATUS status;
HsaCounterProperties *props;
// 1. 查询可用计数器
status = hsaKmtPmcGetCounterProperties(nodeId, &props);
if (status != HSAKMT_STATUS_SUCCESS) {
printf("Failed to get counter properties\n");
return -1;
}
// 2. 选择要监控的计数器
// 假设选择SQ块的前4个计数器
HsaCounter counters[4];
HsaCounterBlockProperties *sq_block = find_sq_block(props);
if (!sq_block || sq_block->NumCounters < 4) {
printf("Not enough SQ counters\n");
return -1;
}
// 检查并发限制
if (sq_block->NumConcurrent < 4) {
printf("SQ block doesn't support 4 concurrent counters\n");
return -1;
}
for (int i = 0; i < 4; i++) {
counters[i] = sq_block->Counters[i];
}
// 3. 注册跟踪
HsaPmcTraceRoot traceRoot;
status = hsaKmtPmcRegisterTrace(nodeId, 4, counters, &traceRoot);
if (status != HSAKMT_STATUS_SUCCESS) {
printf("Failed to register trace: %d\n", status);
return -1;
}
// 4. 打印注册信息
printf("Trace registered successfully!\n");
printf(" TraceId: 0x%lx\n", traceRoot.TraceId);
printf(" Number of passes: %d\n", traceRoot.NumberOfPasses);
printf(" Min buffer size: %lu bytes\n",
traceRoot.TraceBufferMinSizeBytes);
// 5. 验证TraceId
struct perf_trace *trace =
(struct perf_trace *)PORT_UINT64_TO_VPTR(traceRoot.TraceId);
printf("\nTrace structure details:\n");
printf(" Magic: 0x%08x (expected: 0x%08x)\n",
trace->magic4cc, HSA_PERF_MAGIC4CC);
printf(" GPU ID: %d\n", trace->gpu_id);
printf(" State: %d (0=STOPPED, 1=STARTED)\n", trace->state);
printf(" Num blocks: %d\n", trace->num_blocks);
for (int i = 0; i < trace->num_blocks; i++) {
printf(" Block %d:\n", i);
printf(" Block ID: %d\n", trace->blocks[i].block_id);
printf(" Num counters: %d\n", trace->blocks[i].num_counters);
printf(" Counter IDs: ");
for (int j = 0; j < trace->blocks[i].num_counters; j++) {
printf("0x%lx ", trace->blocks[i].counter_id[j]);
}
printf("\n");
}
return 0;
}
8.2 多块注册示例
int register_multi_block_trace(HSAuint32 nodeId)
{
HsaCounterProperties *props;
hsaKmtPmcGetCounterProperties(nodeId, &props);
// 选择来自不同块的计数器
HsaCounter counters[10];
int idx = 0;
// SQ块:4个计数器
HsaCounterBlockProperties *sq_block = find_block_by_uuid(
props, HSA_PROFILEBLOCK_AMD_SQ);
if (sq_block && sq_block->NumConcurrent >= 4) {
for (int i = 0; i < 4 && idx < 10; i++) {
counters[idx++] = sq_block->Counters[i];
}
}
// TCC块:2个计数器
HsaCounterBlockProperties *tcc_block = find_block_by_uuid(
props, HSA_PROFILEBLOCK_AMD_TCC);
if (tcc_block && tcc_block->NumConcurrent >= 2) {
for (int i = 0; i < 2 && idx < 10; i++) {
counters[idx++] = tcc_block->Counters[i];
}
}
// TCP块:2个计数器
HsaCounterBlockProperties *tcp_block = find_block_by_uuid(
props, HSA_PROFILEBLOCK_AMD_TCP);
if (tcp_block && tcp_block->NumConcurrent >= 2) {
for (int i = 0; i < 2 && idx < 10; i++) {
counters[idx++] = tcp_block->Counters[i];
}
}
// MC块:2个计数器
HsaCounterBlockProperties *mc_block = find_block_by_uuid(
props, HSA_PROFILEBLOCK_AMD_MC);
if (mc_block && mc_block->NumConcurrent >= 2) {
for (int i = 0; i < 2 && idx < 10; i++) {
counters[idx++] = mc_block->Counters[i];
}
}
// 注册跟踪
HsaPmcTraceRoot traceRoot;
HSAKMT_STATUS status = hsaKmtPmcRegisterTrace(
nodeId, idx, counters, &traceRoot);
if (status != HSAKMT_STATUS_SUCCESS) {
printf("Multi-block trace registration failed: %d\n", status);
return -1;
}
printf("Multi-block trace registered with %d counters\n", idx);
printf("Expected blocks: 4 (SQ, TCC, TCP, MC)\n");
struct perf_trace *trace =
(struct perf_trace *)PORT_UINT64_TO_VPTR(traceRoot.TraceId);
printf("Actual blocks: %d\n", trace->num_blocks);
return 0;
}
8.3 错误处理示例
int register_with_error_handling(HSAuint32 nodeId)
{
HsaCounterProperties *props;
HSAKMT_STATUS status;
// 查询计数器属性
status = hsaKmtPmcGetCounterProperties(nodeId, &props);
if (status != HSAKMT_STATUS_SUCCESS) {
fprintf(stderr, "Error: Failed to query counter properties "
"(status=%d)\n", status);
return -1;
}
// 选择计数器(可能超过限制)
int desired_count = 20;
HsaCounter *counters = calloc(desired_count, sizeof(HsaCounter));
if (!counters) {
fprintf(stderr, "Error: Failed to allocate counter array\n");
return -1;
}
// 填充计数器(省略具体逻辑)
// ...
// 验证选择
if (!validate_counter_selection(nodeId, counters, desired_count)) {
fprintf(stderr, "Error: Counter selection exceeds "
"concurrent limits\n");
free(counters);
return -1;
}
// 注册跟踪
HsaPmcTraceRoot traceRoot;
status = hsaKmtPmcRegisterTrace(nodeId, desired_count,
counters, &traceRoot);
free(counters); // 注册后可以释放
switch (status) {
case HSAKMT_STATUS_SUCCESS:
printf("Trace registered: TraceId=0x%lx\n", traceRoot.TraceId);
return 0;
case HSAKMT_STATUS_NO_MEMORY:
fprintf(stderr, "Error: Out of memory during registration\n");
return -1;
case HSAKMT_STATUS_INVALID_PARAMETER:
fprintf(stderr, "Error: Invalid parameters provided\n");
return -1;
case HSAKMT_STATUS_INVALID_NODE_UNIT:
fprintf(stderr, "Error: Invalid node ID: %d\n", nodeId);
return -1;
default:
fprintf(stderr, "Error: Unknown error during registration "
"(status=%d)\n", status);
return -1;
}
}
// 验证计数器选择的辅助函数
bool validate_counter_selection(HSAuint32 nodeId,
HsaCounter *counters,
int count)
{
HsaCounterProperties *props;
hsaKmtPmcGetCounterProperties(nodeId, &props);
// 统计每个块的计数器数量
int block_counts[PERFCOUNTER_BLOCKID__MAX] = {0};
for (int i = 0; i < count; i++) {
if (counters[i].BlockIndex >= PERFCOUNTER_BLOCKID__MAX) {
fprintf(stderr, "Invalid block index: %d\n",
counters[i].BlockIndex);
return false;
}
block_counts[counters[i].BlockIndex]++;
}
// 检查每个块的并发限制
HsaCounterBlockProperties *block = &props->Blocks[0];
for (int i = 0; i < props->NumBlocks; i++) {
int block_id = block->Counters[0].BlockIndex;
if (block_counts[block_id] > block->NumConcurrent) {
fprintf(stderr, "Block %d: requested %d counters, "
"limit is %d\n",
block_id, block_counts[block_id],
block->NumConcurrent);
return false;
}
block = (HsaCounterBlockProperties *)
&block->Counters[block->NumCounters];
}
return true;
}
9. 性能考量
9.1 内存开销分析
固定开销:
perf_trace结构: 48字节
临时数组: 23 × 512 × 8 = 94,208字节(注册时)
可变开销(取决于选择的计数器数量):
每个块: 32字节
每个计数器: 8字节(ID) + 4字节(fd) = 12字节
示例:10个计数器,分布在3个块
total = 48 + 32×3 + 12×10 = 264字节
9.2 时间复杂度
操作 时间复杂度
──────────────────────────────────
参数验证 O(1)
第一轮遍历(统计) O(N) N=NumberOfCounters
第二轮遍历(验证限制) O(B) B=PERFCOUNTER_BLOCKID__MAX(23)
内存分配 O(1)
第三轮遍历(填充) O(B)
总体 O(N + B) ≈ O(N)
9.3 优化建议
- 批量注册: 一次注册所有需要的计数器,避免多次注册
- 预验证: 在注册前验证计数器选择,减少失败概率
- 缓存属性: 复用计数器属性查询结果
- 合理选择: 只监控关键计数器,减少资源占用
10. 常见问题
Q1: 为什么需要三轮遍历?
A: 每轮遍历有不同的目的:
- 第一轮: 统计和分组,计算内存需求
- 第二轮: 验证并发限制
- 第三轮: 填充最终数据结构
不能合并是因为第一轮需要完整统计后才能分配内存。
Q2: TraceId 是线程安全的吗?
A: TraceId本身只是一个指针,线程安全性取决于使用方式:
- 单线程使用: 安全
- 多线程读取: 安全(只读操作)
- 多线程修改: 不安全(需要外部同步)
Q3: 可以多次注册同一个GPU吗?
A: 可以,但每次注册都会创建独立的跟踪会话。注意:
- 不同TraceId之间相互独立
- 硬件并发限制是全局的
- 可能需要分时复用
Q4: 注册失败后资源会泄漏吗?
A: 不会,所有错误路径都会清理资源:
no_memory_exit:
free(counter_id); // 清理临时数组
return HSAKMT_STATUS_NO_MEMORY;
Q5: 为什么需要PAGE_ALIGN_UP?
A: 页对齐的好处:
- 性能: 减少TLB未命中
- 兼容性: 某些系统调用要求页对齐
- 效率: 简化内存管理
11. 总结
本文深入分析了 hsaKmtPmcRegisterTrace() 的实现:
核心功能:
- 验证计数器选择的合法性和并发限制
- 按硬件块组织计数器
- 单次分配所有必需的内存
- 建立紧凑的内存布局
- 返回可验证的TraceId
设计亮点:
- 三轮遍历: 清晰的职责分离
- 单次分配: 简化内存管理
- 紧凑布局: 提高缓存效率
- 魔数验证: 检测无效TraceId
- 完善清理: 所有错误路径都释放资源
使用要点:
- 注册前验证计数器选择
- 注意硬件并发限制
- 保存TraceId供后续使用
- 使用完毕后及时注销
在下一篇中,我们将探讨 hsaKmtPmcAcquireTraceAccess() 和 hsaKmtPmcReleaseTraceAccess() 的实现,分析访问权限管理机制。
989

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



