ROCm HIP Graph 概念与机制分析-第 3 章 数据结构:辅助资源管理

本章目标:第 2 章讲的是"图长什么样",这一章讲"图跑起来要靠哪些幕后资源"。GraphExec 之所以能在重放时做到近乎零 CPU 开销,靠的是三个预分配的资源管理器------kernel 参数池、HW 信号池、用户对象------以及一套贯穿全子系统的"静态注册表 + 有效性检查"防御模式。理解它们,才能真正读懂后面"实例化时准备好一切、执行时直接复用"的调度章节。


3.1 为什么需要"资源池"

回顾第 1 章的总纲------空间换时间。重放一张图时,每个 kernel 节点都需要两样东西:

  1. 一块kernel 参数内存(kernarg),把实参打包好给 GPU 读。
  2. 一个或多个完成信号(HW signal / event),让运行时知道这一步什么时候在 GPU 上做完。

如果每次 Run() 都临时 malloc 参数内存、临时 createSignal,那 CPU 端开销一点没省------又回到了裸 launch 的老路。所以 GraphExec 的思路是:Init()(实例化)时一次性把这些资源池建好,重放时只从池子里"借"、用完"还",永不在热路径上创建

这三样幕后资源分别由三个类管理:

管理什么 基类 关键手法
GraphKernelArgManager kernel 参数内存池 amd::ReferenceCountedObject + amd::GraphKernelArgManager 分块(chunk)+ bump 指针分配
GraphSignalManager HW 完成信号集合池 amd::ReferenceCountedObject 预建 N 组信号,借/还复用
UserObject 用户对象生命周期 amd::ReferenceCountedObject 引用计数 + 析构回调

三者都继承 amd::ReferenceCountedObject------因为它们可能被多个持有者(GraphExec、子图、多个流)共享,用引用计数来决定何时真正释放。


3.2 GraphKernelArgManager:kernel 参数内存池

先看类定义(节选):

cpp 复制代码
class GraphKernelArgManager : public amd::ReferenceCountedObject,
                              public amd::GraphKernelArgManager {
 public:
  //! 为给定大小在 device 上分配一整块 kernarg 池
  bool AllocGraphKernargPool(size_t pool_size, amd::Device* device);

  //! 从当前 chunk 按 size/alignment 切一小块 kernel 参数;
  //! 当前 chunk 用满就再分配一整块新 chunk
  address AllocKernArg(size_t size, size_t alignment, int devId) override;

  //! HDP flush;若 flush 寄存器无效则回退到 readback
  void ReadBackOrFlush();

 private:
  struct KernelArgPoolGraph {
    address kernarg_pool_addr_;   //! 池基址
    size_t kernarg_pool_size_;    //! 池大小
    size_t kernarg_pool_offset_;  //! 当前分配偏移
  };
  bool device_kernarg_pool_ = false;  //! 参数池是否在设备内存
  std::unordered_map<amd::Device*, std::vector<KernelArgPoolGraph>>
      kernarg_graph_;  //! 每设备一组已分配的 kernarg 池
};

它的核心是一个经典的 arena / bump-pointer 分配器
#mermaid-svg-cjR2AtCve6ERLysl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:22px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cjR2AtCve6ERLysl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cjR2AtCve6ERLysl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cjR2AtCve6ERLysl .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-cjR2AtCve6ERLysl .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-cjR2AtCve6ERLysl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cjR2AtCve6ERLysl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cjR2AtCve6ERLysl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cjR2AtCve6ERLysl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cjR2AtCve6ERLysl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cjR2AtCve6ERLysl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cjR2AtCve6ERLysl .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-cjR2AtCve6ERLysl .marker.cross{stroke:#0b0b0b;}#mermaid-svg-cjR2AtCve6ERLysl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:22px;}#mermaid-svg-cjR2AtCve6ERLysl p{margin:0;}#mermaid-svg-cjR2AtCve6ERLysl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cjR2AtCve6ERLysl .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-cjR2AtCve6ERLysl .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-cjR2AtCve6ERLysl .cluster-label span p{background-color:transparent;}#mermaid-svg-cjR2AtCve6ERLysl .label text,#mermaid-svg-cjR2AtCve6ERLysl span{fill:#333;color:#333;}#mermaid-svg-cjR2AtCve6ERLysl .node rect,#mermaid-svg-cjR2AtCve6ERLysl .node circle,#mermaid-svg-cjR2AtCve6ERLysl .node ellipse,#mermaid-svg-cjR2AtCve6ERLysl .node polygon,#mermaid-svg-cjR2AtCve6ERLysl .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-cjR2AtCve6ERLysl .rough-node .label text,#mermaid-svg-cjR2AtCve6ERLysl .node .label text,#mermaid-svg-cjR2AtCve6ERLysl .image-shape .label,#mermaid-svg-cjR2AtCve6ERLysl .icon-shape .label{text-anchor:middle;}#mermaid-svg-cjR2AtCve6ERLysl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cjR2AtCve6ERLysl .rough-node .label,#mermaid-svg-cjR2AtCve6ERLysl .node .label,#mermaid-svg-cjR2AtCve6ERLysl .image-shape .label,#mermaid-svg-cjR2AtCve6ERLysl .icon-shape .label{text-align:center;}#mermaid-svg-cjR2AtCve6ERLysl .node.clickable{cursor:pointer;}#mermaid-svg-cjR2AtCve6ERLysl .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-cjR2AtCve6ERLysl .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-cjR2AtCve6ERLysl .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-cjR2AtCve6ERLysl .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-cjR2AtCve6ERLysl .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-cjR2AtCve6ERLysl .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-cjR2AtCve6ERLysl .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-cjR2AtCve6ERLysl .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-cjR2AtCve6ERLysl .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-cjR2AtCve6ERLysl .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-cjR2AtCve6ERLysl .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-cjR2AtCve6ERLysl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cjR2AtCve6ERLysl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cjR2AtCve6ERLysl rect.text{fill:none;stroke-width:0;}#mermaid-svg-cjR2AtCve6ERLysl .icon-shape,#mermaid-svg-cjR2AtCve6ERLysl .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-cjR2AtCve6ERLysl .icon-shape p,#mermaid-svg-cjR2AtCve6ERLysl .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-cjR2AtCve6ERLysl .icon-shape .label rect,#mermaid-svg-cjR2AtCve6ERLysl .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-cjR2AtCve6ERLysl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cjR2AtCve6ERLysl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cjR2AtCve6ERLysl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 够
不够
AllocKernArg

size/align
当前 chunk

够放吗?
对齐 offset

返回地址

offset += size
AllocGraphKernargPool

再要一整块 chunk

  • kernarg_graph_Device → vector<chunk>:每个设备一串已分配的大块(chunk)。多设备场景下互不干扰。
  • AllocKernArg 只是移动偏移量 :在当前 chunk 里,把 kernarg_pool_offset_alignment 对齐,切出 size 字节返回,然后把 offset 往后推。这是 O(1) 且无锁竞争的极快分配。
  • 满了才向底层要新块 :只有当前 chunk 装不下时,才调用 AllocGraphKernargPool 一次性申请一大块新 chunk。也就是说,底层昂贵的显存/主机内存分配被摊薄到极少数次。
  • 析构统一归还~GraphKernelArgManager 遍历 kernarg_graph_,把每个 chunk 用 hostFree 整块释放------不是逐个小分配释放,而是整块回收,干净利落。
cpp 复制代码
~GraphKernelArgManager() {
  for (auto kernarg : kernarg_graph_) {
    for (auto& element : kernarg.second) {
      kernarg.first->hostFree(element.kernarg_pool_addr_, element.kernarg_pool_size_);
    }
    kernarg.second.clear();
  }
}

ReadBackOrFlush() 与内存一致性 :kernarg 可能位于设备可见内存,CPU 写完参数后,需要确保 GPU 能看到最新值。它优先走 HDP flush (硬件的一致性刷新路径),当 flush 寄存器不可用时回退到 readback(读一下强制同步)。这属于 GPU 内存一致性的细节,知道"写完参数要 flush 一下让 GPU 看见"即可。

一句话:GraphKernelArgManager 把"每个 kernel 一次 malloc"变成了"整张图共享几大块,内部 bump 指针切分",这是参数内存层面的空间换时间。


3.3 GraphSignalManager:HW 信号集合池

第二个资源是完成信号。类定义清清楚楚写明了设计意图:

cpp 复制代码
//! Per-GraphExec pool of HW event sets.
class GraphSignalManager : public amd::ReferenceCountedObject {
 public:
  //! 在实例化时预建 num_sets 组、每组 count 个信号,
  //! 这样 launch 永远不必在热路径上创建信号
  bool Prepopulate(amd::Device* device, int count, int num_sets);

  //! 为一次 launch 借一组就绪信号;从空闲池弹出;
  //! 只有池意外为空时才兜底新建一组
  bool AcquireSet(amd::Device* device, int count, std::vector<void*>& out_set);

  //! 归还一组用过的信号并重新 arm,放回空闲池。
  //! 由 launch 完成回调调用------此时保证该 launch 的 GPU 工作已结束
  void ReleaseSet(amd::Device* device, std::vector<void*>& set);

 private:
  std::mutex lock_;
  //! 每设备一叠空闲信号集
  std::unordered_map<amd::Device*, std::vector<std::vector<void*>>> free_sets_;
};

它是一个标准的对象池(Object Pool),工作节奏是"借---用---还"三拍:
#mermaid-svg-3tMTjLdSCSTbTMx0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:22px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3tMTjLdSCSTbTMx0 .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-3tMTjLdSCSTbTMx0 .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3tMTjLdSCSTbTMx0 .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-3tMTjLdSCSTbTMx0 .marker.cross{stroke:#0b0b0b;}#mermaid-svg-3tMTjLdSCSTbTMx0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:22px;}#mermaid-svg-3tMTjLdSCSTbTMx0 p{margin:0;}#mermaid-svg-3tMTjLdSCSTbTMx0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3tMTjLdSCSTbTMx0 .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-3tMTjLdSCSTbTMx0 .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-3tMTjLdSCSTbTMx0 .cluster-label span p{background-color:transparent;}#mermaid-svg-3tMTjLdSCSTbTMx0 .label text,#mermaid-svg-3tMTjLdSCSTbTMx0 span{fill:#333;color:#333;}#mermaid-svg-3tMTjLdSCSTbTMx0 .node rect,#mermaid-svg-3tMTjLdSCSTbTMx0 .node circle,#mermaid-svg-3tMTjLdSCSTbTMx0 .node ellipse,#mermaid-svg-3tMTjLdSCSTbTMx0 .node polygon,#mermaid-svg-3tMTjLdSCSTbTMx0 .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-3tMTjLdSCSTbTMx0 .rough-node .label text,#mermaid-svg-3tMTjLdSCSTbTMx0 .node .label text,#mermaid-svg-3tMTjLdSCSTbTMx0 .image-shape .label,#mermaid-svg-3tMTjLdSCSTbTMx0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-3tMTjLdSCSTbTMx0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3tMTjLdSCSTbTMx0 .rough-node .label,#mermaid-svg-3tMTjLdSCSTbTMx0 .node .label,#mermaid-svg-3tMTjLdSCSTbTMx0 .image-shape .label,#mermaid-svg-3tMTjLdSCSTbTMx0 .icon-shape .label{text-align:center;}#mermaid-svg-3tMTjLdSCSTbTMx0 .node.clickable{cursor:pointer;}#mermaid-svg-3tMTjLdSCSTbTMx0 .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-3tMTjLdSCSTbTMx0 .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-3tMTjLdSCSTbTMx0 .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-3tMTjLdSCSTbTMx0 .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-3tMTjLdSCSTbTMx0 .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-3tMTjLdSCSTbTMx0 .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-3tMTjLdSCSTbTMx0 .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-3tMTjLdSCSTbTMx0 .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-3tMTjLdSCSTbTMx0 .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-3tMTjLdSCSTbTMx0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3tMTjLdSCSTbTMx0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3tMTjLdSCSTbTMx0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-3tMTjLdSCSTbTMx0 .icon-shape,#mermaid-svg-3tMTjLdSCSTbTMx0 .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-3tMTjLdSCSTbTMx0 .icon-shape p,#mermaid-svg-3tMTjLdSCSTbTMx0 .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-3tMTjLdSCSTbTMx0 .icon-shape .label rect,#mermaid-svg-3tMTjLdSCSTbTMx0 .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-3tMTjLdSCSTbTMx0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3tMTjLdSCSTbTMx0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3tMTjLdSCSTbTMx0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AcquireSet 借
GPU 完成回调

ReleaseSet 还
Prepopulate

实例化时预建 N 组
free_sets_

空闲池
一次 launch

使用信号集

关键设计点:

  • PrepopulateInit() 阶段调用 :一次性建好 num_sets 组信号,每组 count 个。之后热路径上的 launch 只借不建。
  • AcquireSet 优先从空闲池弹出:注释里明确说"only creates a new set as a fallback if the pool is unexpectedly empty"------正常情况下永远命中池子,兜底才新建。这保证了绝大多数重放的信号获取是 O(1) 且零系统调用。
  • ReleaseSet 由完成回调触发 :注意归还的时机------launch 完成回调 。为什么非要等回调?因为只有 GPU 真正执行完这一批,信号才空闲下来、可以安全地"re-arm(重新装填)"再借给下一次 launch。这是一个并发正确性 的要点:信号的复用严格由 GPU 完成事件驱动,lock_ 保护 free_sets_ 的并发存取。
  • 为什么叫"集合(set)"而非单个信号:一次图 launch 内部可能有多段/多流,需要一组信号同时协作(第 6、7 章的分段与同步计划会用到),所以池子的最小借还单位是"一组信号"。

GraphSignalManager 把"每次执行 create/destroy 一堆信号"变成了"实例化时建好、循环里借还复用",是同步原语层面的空间换时间。它和第 7 章的 SyncPlan、以及 launch 完成回调 OnLaunchComplete 是一条线上的伙伴。


3.4 UserObject:引用计数与生命周期

第三个类和"性能"关系不大,管的是用户对象的生命周期 ------用户通过 hipUserObjectCreate 往图上挂一个带析构回调的对象(比如一块需要在图销毁时释放的资源),交给运行时托管。

cpp 复制代码
class UserObject : public amd::ReferenceCountedObject {
  typedef void (*UserCallbackDestructor)(void* data);
 public:
  //! 哪些 Graph 拥有这个 user object
  std::unordered_set<Graph*> owning_graphs_;

  UserObject(UserCallbackDestructor callback, void* data, unsigned int flags)
      : ReferenceCountedObject(), callback_(callback), data_(data), flags_(flags) {
    amd::ScopedLock lock(UserObjectLock_);
    ObjectSet_.insert(this);              // 构造即登记
  }

  virtual ~UserObject() {
    amd::ScopedLock lock(UserObjectLock_);
    if (callback_ != nullptr) {
      callback_(data_);                   // 析构时回调用户释放函数
    }
    ObjectSet_.erase(this);               // 从注册表移除
    owning_graphs_.clear();
  }

  void increaseRefCount(const unsigned int refCount) {
    for (uint32_t i = 0; i < refCount; i++) retain();
  }
  void decreaseRefCount(const unsigned int refCount) {
    assert((refCount <= referenceCount()) && "count is bigger than refcount");
    for (uint32_t i = 0; i < refCount; i++) release();
  }
  // ...
 private:
  UserCallbackDestructor callback_;
  void* data_;
  unsigned int flags_;
  static std::unordered_set<UserObject*> ObjectSet_;
  static amd::Monitor UserObjectLock_;
};

几个要点:

  • 引用计数即生命周期UserObject 继承 amd::ReferenceCountedObject,用 retain()/release() 计数。HIP 的 user object API 允许一次增减多个引用,于是有了 increaseRefCount/decreaseRefCount 这种"批量 retain/release"的封装。当计数归零,对象析构。
  • 析构回调 :析构时若 callback_ 非空,就调用它并把 data_ 传回去------这正是用户注册的"东西没人用了就这么释放"的钩子。这让运行时能安全托管任意用户资源。
  • owning_graphs_ 记录归属 :一个 user object 可以被多张图引用。注释点明了一个易错点------当对象即将被删除(最后一次 release())时,必须把指向它的指针从各个 Graph 的 user object 列表里清掉,否则又是悬垂指针。
  • ObjectSet_ + isUserObjvalid:和节点一样,构造登记、析构注销,配一个静态有效性检查------这就引出了本章最后、也是最重要的一个横切模式。

3.5 横切模式:静态注册表 + 有效性检查

到这里,你应该已经在四个不同的类里看到了同一套代码骨架:

注册表 有效性检查
GraphNode nodeSet_ isNodeValid nodeSetLock_
Graph graphSet_ isGraphValid (对应 set 锁)
GraphExec graphExecSet_ isGraphExecValid graphExecSetLock_
UserObject ObjectSet_ isUserObjvalid UserObjectLock_

它们的模板完全一致:

cpp 复制代码
// 1) 一个静态集合登记所有"活着"的对象
static std::unordered_set<T*> XxxSet_;
static amd::Monitor XxxLock_;   // 或 recursive_mutex / shared_mutex

// 2) 构造时登记
T(...) { amd::ScopedLock lock(XxxLock_); XxxSet_.insert(this); }

// 3) 析构时注销
~T() { amd::ScopedLock lock(XxxLock_); XxxSet_.erase(this); }

// 4) 静态有效性检查:句柄是否还活着
static bool isXxxValid(T* p) {
  amd::ScopedLock lock(XxxLock_);
  return p != nullptr && XxxSet_.find(p) != XxxSet_.end();
}

这个模式解决什么问题? HIP 是 C 风格 API:用户拿到的是不透明句柄 hipGraph_thipGraphNode_thipGraphExec_thipUserObject_t,本质就是裸指针。用户完全可能:

  • 传一个已经销毁的句柄(use-after-free);
  • 传一个根本不是该类型的野指针;
  • 在多线程里一边销毁一边使用。

如果运行时直接解引用,就是崩溃或更隐蔽的内存破坏。静态注册表把"这个指针是不是我发出去的、现在还活着吗"变成了一次 O(1) 的哈希查找 ,把非法句柄挡在 API 边界之外。这是一道纯粹的防御性安全网(对应 OWASP 里"不受信任输入校验"的思路,只不过这里的不受信任输入是 API 句柄)。

代价与权衡

  • 每次对象构造/析构都要抢一次全局锁、动一次全局集合------有一定开销,但相对于图的创建/销毁频率(远低于重放频率)完全可接受。
  • 注册表是进程级全局 的,所以配套的锁不能少,GraphExec 甚至用到 recursive_mutexshared_mutex(读多写少时用共享锁)来降低竞争。
  • 它只能保证"指针曾经是一个有效对象且尚未析构",不能防住"指针被 realloc 复用到同地址"这种极端 ABA,但在实践中足够可靠。

一句话记住这个模式:凡是会以裸指针句柄暴露给用户的类,都用"静态 set 登记 + isXxxValid 查表"来做悬垂/非法句柄防护 。第 2 章的 nodeSet_、本章的三个资源类,都是它的实例。


3.6 三类资源在生命周期中的位置

把本章三个资源管理器放回第 1 章的生命周期时间轴上,能看得更清楚它们各自的活跃区间:
#mermaid-svg-pU8mwFdFuJ27zs8i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:22px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pU8mwFdFuJ27zs8i .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-pU8mwFdFuJ27zs8i .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pU8mwFdFuJ27zs8i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pU8mwFdFuJ27zs8i .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-pU8mwFdFuJ27zs8i .marker.cross{stroke:#0b0b0b;}#mermaid-svg-pU8mwFdFuJ27zs8i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:22px;}#mermaid-svg-pU8mwFdFuJ27zs8i p{margin:0;}#mermaid-svg-pU8mwFdFuJ27zs8i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pU8mwFdFuJ27zs8i .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-pU8mwFdFuJ27zs8i .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-pU8mwFdFuJ27zs8i .cluster-label span p{background-color:transparent;}#mermaid-svg-pU8mwFdFuJ27zs8i .label text,#mermaid-svg-pU8mwFdFuJ27zs8i span{fill:#333;color:#333;}#mermaid-svg-pU8mwFdFuJ27zs8i .node rect,#mermaid-svg-pU8mwFdFuJ27zs8i .node circle,#mermaid-svg-pU8mwFdFuJ27zs8i .node ellipse,#mermaid-svg-pU8mwFdFuJ27zs8i .node polygon,#mermaid-svg-pU8mwFdFuJ27zs8i .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-pU8mwFdFuJ27zs8i .rough-node .label text,#mermaid-svg-pU8mwFdFuJ27zs8i .node .label text,#mermaid-svg-pU8mwFdFuJ27zs8i .image-shape .label,#mermaid-svg-pU8mwFdFuJ27zs8i .icon-shape .label{text-anchor:middle;}#mermaid-svg-pU8mwFdFuJ27zs8i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pU8mwFdFuJ27zs8i .rough-node .label,#mermaid-svg-pU8mwFdFuJ27zs8i .node .label,#mermaid-svg-pU8mwFdFuJ27zs8i .image-shape .label,#mermaid-svg-pU8mwFdFuJ27zs8i .icon-shape .label{text-align:center;}#mermaid-svg-pU8mwFdFuJ27zs8i .node.clickable{cursor:pointer;}#mermaid-svg-pU8mwFdFuJ27zs8i .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-pU8mwFdFuJ27zs8i .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-pU8mwFdFuJ27zs8i .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-pU8mwFdFuJ27zs8i .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-pU8mwFdFuJ27zs8i .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-pU8mwFdFuJ27zs8i .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-pU8mwFdFuJ27zs8i .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-pU8mwFdFuJ27zs8i .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-pU8mwFdFuJ27zs8i .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-pU8mwFdFuJ27zs8i .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-pU8mwFdFuJ27zs8i .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-pU8mwFdFuJ27zs8i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pU8mwFdFuJ27zs8i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pU8mwFdFuJ27zs8i rect.text{fill:none;stroke-width:0;}#mermaid-svg-pU8mwFdFuJ27zs8i .icon-shape,#mermaid-svg-pU8mwFdFuJ27zs8i .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-pU8mwFdFuJ27zs8i .icon-shape p,#mermaid-svg-pU8mwFdFuJ27zs8i .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-pU8mwFdFuJ27zs8i .icon-shape .label rect,#mermaid-svg-pU8mwFdFuJ27zs8i .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-pU8mwFdFuJ27zs8i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pU8mwFdFuJ27zs8i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pU8mwFdFuJ27zs8i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 销毁 Destroy
重放 Run 循环
实例化 Init
下一次借
GraphKernelArgManager

建 kernarg 大块
GraphSignalManager

Prepopulate 预建信号组
AllocKernArg

bump 切参数
AcquireSet

借信号组
GPU 完成回调

ReleaseSet 还信号
~GraphKernelArgManager

整块 hostFree
UserObject 析构回调

释放用户资源

  • 参数池Init() 建块 → 每次 Run() bump 切分 → Destroy 整块归还。
  • 信号池Init() 预建 → 每次 Run() 借 → GPU 完成回调还 → 循环复用。
  • 用户对象:贯穿始终,由引用计数决定寿命,归零时触发析构回调释放用户资源。

这三条线,正是第 5~7 章调度执行"零成本重放"的物质基础------调度算法再巧,也得有现成的参数内存和信号可用。


3.7 本章小结

  • GraphExec 的高性能重放建立在三个预分配资源管理器 之上,全部继承 amd::ReferenceCountedObject 以支持共享与引用计数生命周期。
  • GraphKernelArgManagerbump-pointer arena :按设备维护若干大块 chunk,AllocKernArg 只移动偏移量,满了才要新块,析构整块回收;ReadBackOrFlush 处理写后一致性。
  • GraphSignalManager对象池Prepopulate 实例化时预建信号组,AcquireSet 借、ReleaseSet(由 GPU 完成回调)还,严格由完成事件驱动复用,lock_ 保护并发。
  • UserObject引用计数 + 析构回调 托管用户资源,owning_graphs_ 记录归属,最后一次 release 时须清理各图的引用。
  • 贯穿全子系统的横切模式是静态注册表 + isXxxValidnodeSet_/graphSet_/graphExecSet_/ObjectSet_),把非法/悬垂句柄挡在 API 边界外,是一道 O(1) 的防御性安全网。

下一章进入生命周期:构建与实例化 ,我们会看两种建图方式(手动 API vs 流捕获)、ihipGraphAddNode 如何落地一次"带依赖的加节点",以及 GraphExec::Init() 究竟在实例化阶段预计算了哪些东西、如何预分配 stream------把本章的资源池真正"点燃"。

相关推荐
DeeplyMind1 天前
ROCm HIP Graph 概念与机制分析- 第 2 章 数据结构:节点与图
hip graph