ROCm rocr-libhsakmt性能跟踪与分析系列10-3:跟踪注册与资源管理

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_UNITNodeId无效

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;
    }

多层验证

  1. 内存分配成功性
  2. 全局状态有效性
  3. 参数完整性
  4. 节点ID有效性
  5. 数量上限

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 单次分配策略

优势

  1. 简化管理: 只需一次malloc/free
  2. 缓存友好: 数据紧密排列,减少缓存未命中
  3. 原子性: 分配失败时无需复杂的清理逻辑

实现

// 一次性分配所有内存
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 优化建议

  1. 批量注册: 一次注册所有需要的计数器,避免多次注册
  2. 预验证: 在注册前验证计数器选择,减少失败概率
  3. 缓存属性: 复用计数器属性查询结果
  4. 合理选择: 只监控关键计数器,减少资源占用

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() 的实现:

核心功能

  1. 验证计数器选择的合法性和并发限制
  2. 按硬件块组织计数器
  3. 单次分配所有必需的内存
  4. 建立紧凑的内存布局
  5. 返回可验证的TraceId

设计亮点

  • 三轮遍历: 清晰的职责分离
  • 单次分配: 简化内存管理
  • 紧凑布局: 提高缓存效率
  • 魔数验证: 检测无效TraceId
  • 完善清理: 所有错误路径都释放资源

使用要点

  • 注册前验证计数器选择
  • 注意硬件并发限制
  • 保存TraceId供后续使用
  • 使用完毕后及时注销

在下一篇中,我们将探讨 hsaKmtPmcAcquireTraceAccess()hsaKmtPmcReleaseTraceAccess() 的实现,分析访问权限管理机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值