Tracy 与 ROCm 下的按需(On-Demand)GPU 性能剖析复现指南:定位 rocprof 后端的三个致命缺陷
【免费下载链接】tracyFrame profiler项目地址: https://gitcode.com/GitHub_Trending/tr/tracy
Tracy 是一款帧级(frame)性能剖析器,其客户端支持通过宏标记 CPU/GPU 区域,并通过TRACY_ON_DEMAND开启"按需剖析"模式——只有剖析器(tracy-capture或 GUI)真正连接到被测程序时,客户端才开始采集数据。当这一模式与 ROCm 的 rocprofiler-sdk 后端(TRACY_ROCPROF)组合使用时,未修补的 Tracy 会在剖析器"迟到"连接时崩溃。本文以仓库中的 on-demand 复现工程 为核心,系统梳理三个根因缺陷、完整的复现与验证步骤,并结合TracyRocprof.cpp、TracyProfiler.hpp等源码解释修复原理,帮助读者在 AMD GPU + ROCm 环境下正确构建、复现、验证并理解该问题的全貌。
一、问题背景:为什么"按需"模式会暴露 GPU 剖析缺陷
1.1 三种宏的作用
复现工程的核心是同时启用三个编译期宏(见 CMakeLists.txt):
set(REPRO_DEFINES TRACY_ENABLE TRACY_ON_DEMAND TRACY_ROCPROF __HIP_PLATFORM_AMD__)TRACY_ENABLE:开启 Tracy 客户端采集功能(不加则所有宏为空操作);TRACY_ON_DEMAND:开启按需模式。客户端默认不连接、不采集,剖析器连接后才启动数据发送;TRACY_ROCPROF:启用基于 rocprofiler-sdk 的 AMD GPU 后端,负责捕获 kernel dispatch、GPU zone 与计数器;__HIP_PLATFORM_AMD__:HIP 平台宏,供 ROCm 头文件使用。
1.2 按需模式的时序矛盾
在按需模式下,TracyIsStarted在剖析器连接之后才被置位。rocprof 后端有一个专门的校准线程等待该标志:
void calibration_thread( void* ptr ) { while( !TracyIsStarted ) ; ToolData* data = static_cast<ToolData*>( ptr ); >auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuNewContext ); ... tracy::MemWrite( &item->gpuNewContext.type, tracy::GpuContextType::Rocprof ); #ifdef TRACY_ON_DEMAND GetProfiler().DeferItem( *item ); #endif tracy::Profiler::QueueSerialFinish();见 TracyRocprof.cpp。
后果:按需模式下剖析器晚连接,客户端此前发出的GpuNewContext已经随着普通串行队列发送完毕(无人接收),又没有被放入延迟队列,因此永远不会被"重放"给迟到连接的服务端。服务端收到针对未知上下文的GpuZoneBegin事件时,会触发断言失败:
Assertion `ctx' failed in ProcessGpuZoneBeginImplCommon服务端处理 GPU 上下文的逻辑位于 TracyWorker.cpp(ProcessGpuNewContext与ProcessGpuZoneBeginImplCommon),上下文不存在时直接断言。
缺陷 2:GpuContextName未被延迟
同一个函数还会写入 GPU 上下文名称"rocprofv3"(常量定义于 TracyRocprof.cpp,发送逻辑见 TracyRocprof.cpp):
auto* item = tracy::Profiler::QueueSerial(); tracy::MemWrite( &item->hdr.type, tracy::QueueType::GpuContextName ); tracy::MemWrite( &item->gpuContextNameFat.context, context_id ); tracy::MemWrite( &item->gpuContextNameFat.ptr, uint64_t( cloned_name ) ); tracy::MemWrite( &item->gpuContextNameFat.size, uint16_t( name_length ) ); #ifdef TRACY_ON_DEMAND GetProfiler().DeferItem( *item ); #endif tracy::Profiler::QueueSerialFinish();后果:即使修复了缺陷 1,晚连接的剖析器虽然能看到 GPU 上下文,却拿不到名字,上下文在剖析器中显示为 unnamed。注意该处的名称字符串必须用tracy_malloc克隆——Tracy 内部会无条件释放该内存,且其分配器与 libc 不同,必须同源分配。
缺陷 3:data->init守卫在初始化前丢掉了 kernel 符号
tool_callback_tracing_callback()顶层曾以data->init作为所有回调的总开关。但data->init只有在校准线程分配 GPU 上下文之后才被置true(见上文calibration_thread),而kernel 符号注册事件(CODE_OBJECT_DEVICE_KERNEL_SYMBOL_REGISTER)发生在 HIP 初始化阶段、即data->init为 false 的窗口内,因此被静默丢弃。即使绕过了崩溃,剖析器中也会缺失 kernel 名称。修复后的代码将符号注册与init守卫解耦,并区分 LOAD/UNLOAD 相位维护client_kernels表:
if( record.kind == ROCPROFILER_CALLBACK_TRACING_CODE_OBJECT && record.operation == ROCPROFILER_CODE_OBJECT_DEVICE_KERNEL_SYMBOL_REGISTER ) { ... if( record.phase == ROCPROFILER_CALLBACK_PHASE_LOAD ) >cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build该 CMake 工程(CMakeLists.txt)会做三件事:
- 以
TRACY_ENABLE TRACY_ON_DEMAND TRACY_ROCPROF __HIP_PLATFORM_AMD__编译 TracyClient.cpp(即开启 rocprof 后端的 Tracy 客户端); - 把 repro.cpp 作为 HIP 源码编译为
repro可执行文件,链接TracyClient与librocprofiler-sdk; - (可选,见下文)在
BUILD_CHECK_TOOL=ON时编译验证工具check_gpu_ctx_name。
4.2 复现程序做了什么
repro.cpp 是一个最小化 HIP 程序:
- 定义
vectorAdd内核(__global__),执行c = a + b; - 分配并初始化 1024 个 float 的输入数组,拷贝到设备端;
- 循环 100 次:每次用
ZoneScopedN( "iteration" )标记 CPU zone、启动一次 kernel、hipDeviceSynchronize()同步、usleep( 100000 )休眠 100ms、FrameMark标记帧——总运行时长约 10 秒,为tracy-capture的连接留出充足窗口; - 回拷结果并校验,打印
Result: PASS或Result: FAIL,以此作为程序退出码。
4.3 触发崩溃
在第一个终端启动被测程序,第二个终端启动捕获器:
./build/repro & tracy-capture -o repro.tracy -s 5./build/repro &后台运行复现程序(程序会打印 "waiting for profiler to connect...");tracy-capture -o repro.tracy -s 5连接并按 5 秒采集,输出到repro.tracy。
在未修补的 Tracy 上,捕获器会因Assertion 'ctx' failed崩溃;完全修补后,捕获成功,生成的repro.tracy包含约 50 个 GPU zone,上下文名为"rocprofv3"。
4.4 通过 ctest 回归
复现器同时注册为 ctest 测试目标(CMakeLists.txt),可一键执行:
ctest --test-dir build -R repro这为 CI 或本地回归验证提供了标准入口。
五、验证工具:check_gpu_ctx_name
为验证"上下文名是否被正确延迟给晚连接客户端",工程提供了一个独立验证工具check_gpu_ctx_name。它加载.tracy捕获文件并打印每个 GPU 上下文的名称,返回码约定:
0:所有上下文都有名字;2:发现未命名上下文;1:参数错误或文件打开/解析失败。
5.1 构建(需显式开启)
该工具链接 Tracy 服务端库(TracyServer),因此默认不构建,仅在请求时编译:
cmake -B build -DBUILD_CHECK_TOOL=ON cmake --build build --target check_gpu_ctx_name从 CMakeLists.txt 可见,它通过引入 cmake/vendor.cmake 与 cmake/server.cmake 以与tracy-capture相同的方式组装服务端及其供应商依赖,并要求 C++20。
5.2 运行与预期输出
./build/check_gpu_ctx_name repro.tracy- 修补后预期:
GPU context 0: rocprofv3 - 未修补(仅修复缺陷 1)预期:
GPU context 0: (unnamed)
其实现(check_gpu_ctx_name.cpp)通过tracy::FileRead::Open打开捕获文件,构造tracy::Worker(EventType::None,不做事件分析),遍历worker.GetGpuData(),用worker.GetString( ctx->name )解析上下文名字符串,并区分"名字未激活/名字为空串"两种情况输出(unnamed)。
六、修复原理:延迟队列(Deferred Queue)机制
缺陷 1 与缺陷 2 的修复方式完全一致:在TRACY_ON_DEMAND下把关键上下文消息送入延迟队列。DeferItem的定义位于 TracyProfiler.hpp:
#ifdef TRACY_ON_DEMAND tracy_force_inline uint64_t ConnectionId() const { ... } tracy_force_inline void DeferItem( const QueueItem& item ) { m_deferredLock.lock(); auto dst = m_deferredQueue.push_next(); memcpy( dst, &item, sizeof( item ) ); m_deferredLock.unlock(); } #endif其语义是:把队列项复制进客户端的延迟队列;当剖析器连接时,客户端会把延迟队列中的消息按顺序重放给服务端。凡是晚连接客户端也需要知道的"状态建立类"消息都必须走DeferItem——这正是其他 GPU 后端(如TracyVulkan.hpp、TracyOpenGL.hpp、TracyD3D11.hpp)中DeferItem被普遍调用的原因(可在 public/tracy 与 public/client 中检索到大量同类调用点)。
需要特别注意的是:普通 zone 数据(GpuZoneBegin/GpuZoneEnd/GpuTime)不需要延迟——它们是时变的事件流,晚连接的剖析器只关心连接之后的事件;而上下文建立消息(GpuNewContext、GpuContextName、GpuAnnotationName等)是后续所有事件的前置条件,必须保证重放。缺陷 1 的致命性就在于此:GpuNewContext缺失导致服务端把后续 zone 事件映射到不存在的上下文,从而触发断言。
七、修复前后行为对照
| Tracy 版本 | 结果 |
|---|---|
| 未修补 | tracy-capture崩溃:Assertion 'ctx' failed |
| 仅修补缺陷 1(GpuNewContext 延迟) | 捕获成功,但 GPU 上下文无名字(缺陷 2 未修) |
| 完全修补(三处缺陷均修复) | 捕获成功,约 50 个 GPU zone,上下文名为"rocprofv3" |
对照表明确说明:只有三处缺陷全部修复,按需模式下的 rocprof 剖析才同时具备"不崩溃"与"信息完整"两个属性。缺陷 1 解决崩溃,缺陷 2 解决上下文命名,缺陷 3 解决 kernel 名称缺失——三者缺一不可。
八、源码指引与延伸阅读
若希望深入阅读相关实现,建议按以下路径浏览当前仓库:
- 复现工程本身:README.md、repro.cpp、check_gpu_ctx_name.cpp、CMakeLists.txt;
- rocprof 后端核心:TracyRocprof.cpp(
gpu_context_allocate、calibration_thread、tool_callback_tracing_callback、tool_init/tool_fini); - 延迟队列机制:TracyProfiler.hpp(
DeferItem与m_deferredQueue); - 服务端 GPU 上下文处理:TracyWorker.cpp(
ProcessGpuNewContext、ProcessGpuZoneBeginImplCommon、ProcessGpuContextName); - 服务端构建方式:cmake/server.cmake、cmake/vendor.cmake(
check_gpu_ctx_name与tracy-capture共用)。
需要注意的是,本文描述的复现与验证均基于当前仓库TRACY_ROCPROF后端已修复的实现;若要在旧版本或本地修改版上复现崩溃,需回到"未调用DeferItem、以data->init门控全部回调"的原始状态,并确保被测程序在剖析器连接前完成 HIP 初始化,才能稳定命中三个缺陷的时序窗口。
【免费下载链接】tracyFrame profiler项目地址: https://gitcode.com/GitHub_Trending/tr/tracy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考