Skip to content

Update profiling buffer pool to SPSC#1287

Open
zmnobug wants to merge 2 commits into
hw-native-sys:mainfrom
zmnobug:issue-1251-spsc-buffer-pool
Open

Update profiling buffer pool to SPSC#1287
zmnobug wants to merge 2 commits into
hw-native-sys:mainfrom
zmnobug:issue-1251-spsc-buffer-pool

Conversation

@zmnobug

@zmnobug zmnobug commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Move the shared profiling buffer pool to per-lane SPSC ready, done, and recycled queues.
  • Keep an independent replenish thread for done-to-recycled recycling while drain lanes refill free queues from shard-local recycled lanes.
  • Preserve SPSC ownership on fallback paths: ready-full drain now retires undelivered buffers instead of writing done, and retired buffers use a mutex-protected exceptional holding pool.
  • Apply the shared buffer-pool path across L2 Swimlane, PMU, DepGen, ScopeStats, and TensorDump, with updated C++ unit tests and docs.
  • Leave the local L2 synthetic benchmark tooling out of this PR.

Testing

  • CCACHE_DIR=/tmp/simpler-ccache cmake --build tests/ut/cpp/build --target test_buffer_pool_manager
  • tests/ut/cpp/build/test_buffer_pool_manager (12/12 passed)
  • CCACHE_DIR=/tmp/simpler-ccache cmake --build tests/ut/cpp/build
  • ctest --test-dir tests/ut/cpp/build -LE requires_hardware --output-on-failure (54/54 passed after rerun outside sandbox; sandboxed run blocked the socket test with Operation not permitted)
  • CCACHE_DIR=/tmp/simpler-ccache .venv/bin/python -m pip install --no-build-isolation -e .
  • git diff --check
  • GitHub PR checks for 893b1f16: all checks passed.

Benchmark

  • Local synthetic benchmark tooling remains outside this PR; all benchmarked trees used the same local synthetic producer tool.

Baseline vs PR head before review fix

  • task-submit: task_20260707_203817_92120128835, baseline and current run sequentially on the same NPU 2.
  • Baseline: latest upstream/main at 3aa7f26d.
  • Current benchmarked tree: PR head a712db9b.
  • Host read throughput over 5 runs:
    • mean: baseline 3.3754 GB/s, current 3.8195 GB/s, +13.16%
    • median: baseline 3.5904 GB/s, current 3.8208 GB/s, +6.42%

Latest PR head retest

  • task-submit: task_20260707_231640_262842832235, latest PR head 893b1f16 plus the same local synthetic WIP, on NPU 3.
  • The local WIP run did not print the synthetic summary line, so host receive was derived from l2_swimlane_records.json as records * 32 bytes / 5 ms.
  • Per-run host receive: 3.8208, 3.8208, 3.8208, 1.9456, 3.8016 GB/s (run 4 was a retained low outlier).
  • Summary vs latest baseline:
    • mean: baseline 3.3754 GB/s, latest retest 3.4419 GB/s, +1.97%
    • median: baseline 3.5904 GB/s, latest retest 3.8208 GB/s, +6.42%
    • stable 4-run mean, excluding the low outlier only as a steady-state reference: 3.8160 GB/s, +13.05%

Notes

  • The latest review fix keeps done shards collector-produced only. Exceptional fallback buffers go to retired holding pools and are released at teardown.
  • The latest retest indicates the review-fix changes did not introduce host-read throughput degradation.

close #1251

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c22eab9-30a9-4b61-91cf-3b7ac9a5631d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

BufferPoolManager and ProfilerAlgorithms are refactored to use per-shard SPSC ring buffers for ready/done/recycled/retired queues, replacing mutex/std::queue mechanisms. Collectors now seed shard-affine recycled lanes via a shard argument to push_recycled. Teardown separates device-allocation release from host-shadow release. Tests and docs are updated accordingly.

Changes

Shard-local SPSC buffer pool redesign

Layer / File(s) Summary
BufferPoolManager SPSC ring contract and core operations
src/common/platform/include/host/buffer_pool_manager.h
Introduces SpscRing/ProfilerModuleHostQueueCapacity, converts ready/done/recycled/retired queues to rings, adds canonicalization helpers (release_pointer_for, claim_release_pointer, block-range resolve_host_ptr), and rewrites release/clear/free paths to separate device release from host-shadow release.
ProfilerAlgorithms drain/refill/replenish logic
src/common/platform/include/host/profiler_base.h
Adds drain_done_for_shard, reworks process_entry/proactive_replenish into replenish_free_queues+warm_recycled_pools, updates obtain_buffer, try_push_to_free_queue, thread-launch asserts, and release_one_buffer to use claim_release_pointer.
Collector shard-aware recycled seeding and teardown comments
src/a2a3/..., src/a5/..., src/common/platform/include/host/{dep_gen,l2_swimlane,scope_stats,tensor_dump}_collector.h, src/common/platform/shared/host/*.cpp
PMU, dep_gen, l2_swimlane, scope_stats, and tensor_dump collectors now compute and pass a shard index to push_recycled, add recycled_warm_target (l2_swimlane), and clarify device-vs-shadow release comments.
BufferPoolManager sharding unit tests
tests/ut/cpp/common/test_buffer_pool_manager.cpp, tests/ut/cpp/CMakeLists.txt
Adds tests for ready-shard fullness/order, wait_pop_ready wakeups, per-shard done draining, startup recycle pop, batch carving with custom MemoryOps, and extended release_owned_buffers coverage; wires host log sources into the test target.
Documentation updates for shard-local recycling and host queues
docs/dfx/*.md, docs/profiling-framework.md
Updates terminology across docs to describe shard-local recycled lanes, refill/freeQ and host ready_q wording, SPSC ring semantics, revised stop() ordering, concurrency invariants, and a5 teardown semantics.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • hw-native-sys/simpler#1162: Both PRs substantially modify profiler_base.h (drain/replenish, queue refill/publish) and buffer_pool_manager.h.

Poem

A rabbit hops through rings so tight,
Shard by shard, refilling right,
No more mutex, no more queue,
SPSC lanes see us through 🐇
Buffers recycled, freed with care,
Docs updated everywhere!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the main change: moving the profiling buffer pool to per-lane SPSC queues.
Description check ✅ Passed The description is directly about the buffer-pool SPSC refactor and related docs/tests.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the profiling framework's buffer management system to improve performance and reduce overhead. It replaces mutex-guarded queues with lock-free SPSC (Single Producer Single Consumer) rings for ready, done, and recycled buffer pools. Additionally, it introduces a batched block allocation strategy where a single large device block is allocated, registered once, and carved into fixed-size sub-buffers, significantly reducing HAL registration costs. Range-based host pointer resolution is implemented to support these carved sub-buffers, and the runtime hot path is optimized to be lock-free and allocation-free. All profiling collectors (PMU, L2Swimlane, DepGen, ScopeStats, and TensorDump) have been updated to adopt this new architecture, accompanied by updated documentation and comprehensive unit tests. I have no further feedback to provide as the implementation is robust and well-tested.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/common/platform/include/host/buffer_pool_manager.h (1)

633-664: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Freed allocation base is never removed from released_allocations_, risking a permanent leak on address reuse.

claim_release_pointer (Line 811-825) inserts release_ptr into released_allocations_ once, permanently blocking any future claim on that address — but free_buffer (Line 633-664) removes the entry from dev_to_host_/block_ranges_ and calls ops_.free_(release_ptr) without ever erasing release_ptr from released_allocations_. If the underlying allocator later hands back the exact same address for a new alloc_and_register_block/register_mapping call within the same collector lifetime (i.e., before a full clear_mappings()/release_all_owned() reset), claim_release_pointer/release_pointer_for will treat that fresh allocation as already released, and any subsequent free_buffer/release_one_buffer call on it will silently no-op — leaking that device (and possibly host-shadow) allocation for the rest of the run.

Erase release_ptr from released_allocations_ inside free_buffer (under mapping_mutex_) once the release is claimed and device memory is actually freed.

Proposed fix
     void free_buffer(void *dev_ptr) {
         if (dev_ptr == nullptr) return;
         void *release_ptr = nullptr;
         if (!claim_release_pointer(dev_ptr, &release_ptr)) return;

         void *host_ptr = nullptr;
         bool free_host_shadow = false;
         {
             std::scoped_lock<std::mutex> lock(mapping_mutex_);
             auto it = dev_to_host_.find(release_ptr);
             host_ptr = (it != dev_to_host_.end()) ? it->second : nullptr;
             if (it != dev_to_host_.end()) {
                 dev_to_host_.erase(it);
             }
             block_ranges_.erase(...);
             free_host_shadow = (host_ptr != nullptr && malloc_shadows_.erase(host_ptr) > 0);
+            released_allocations_.erase(release_ptr);
         }
         if (ops_.free_) {
             ops_.free_(release_ptr);
         }

Also applies to: 802-825

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/platform/include/host/buffer_pool_manager.h` around lines 633 -
664, The free path in free_buffer still leaves the allocation address recorded
in released_allocations_, so a later reuse of the same device pointer can be
treated as already released and leak the new allocation. Update free_buffer,
alongside the existing dev_to_host_ and block_ranges_ cleanup under
mapping_mutex_, to also erase release_ptr from released_allocations_ once the
release has been claimed and ops_.free_ has run; use claim_release_pointer and
free_buffer as the main symbols to locate the change.
🧹 Nitpick comments (1)
src/a2a3/platform/shared/host/pmu_collector.cpp (1)

113-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a retire fallback when push_recycled fails during init, matching allocate_recycled_batch's own pattern.

The return value of push_recycled is discarded ((void)..., Line 124). BufferPoolManager::allocate_recycled_batch treats a full recycled ring as recoverable by falling back to retire_unqueued_buffer so the buffer is still teardown-tracked/accounted for; here a full ring just silently drops the surplus buffer from the pool for the remainder of the run (it will still be freed correctly at finalize() via dev_to_host_, so this isn't a leak — just wasted capacity with no error signal at this call site beyond the manager's internal LOG_ERROR).

Proposed fix
             } else {
                 // Surplus buffers go into the recycled pool, available to any core.
                 int shard = (num_threads > 0) ? (c % num_threads) : c;
-                (void)manager_.push_recycled(0, dev_ptr, shard);
+                if (!manager_.push_recycled(0, dev_ptr, shard)) {
+                    (void)manager_.retire_unqueued_buffer(0, dev_ptr, shard);
+                }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/a2a3/platform/shared/host/pmu_collector.cpp` around lines 113 - 125, The
init path in pmu_collector’s buffer distribution silently ignores a failed
BufferPoolManager::push_recycled call, unlike
BufferPoolManager::allocate_recycled_batch which falls back to
retire_unqueued_buffer when the recycled ring is full. Update the surplus-buffer
branch in the pmu_collector initialization logic to check the push_recycled
return value and, on failure, retire the buffer through the same fallback path
so it stays tracked/accounted for instead of being dropped from the pool for the
rest of the run.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/common/platform/include/host/buffer_pool_manager.h`:
- Around line 153-213: `ProfilerModuleHostQueueCapacity` is using
`Module::kReadyQueueSize` for all ring buffers, which incorrectly ties
recycled/retired pool sizing to the ready queue. Update the capacity trait in
`buffer_pool_manager.h` so `ReadyRing` keeps the ready-queue sizing, but
`DoneRing` and the per-shard `RecycledRing`/retired rings use a capacity derived
from the module’s buffer-pool constants (for example `kSlotCount`,
`kCollectorThreadCount`, or the warm/surplus target) via a separate trait or
specialization. Keep the existing symbols like `SpscRing`,
`ProfilerModuleHostQueueCapacity`, and the ring typedefs intact while splitting
the semantics so warm-up and recycling cannot silently drop buffers.

---

Outside diff comments:
In `@src/common/platform/include/host/buffer_pool_manager.h`:
- Around line 633-664: The free path in free_buffer still leaves the allocation
address recorded in released_allocations_, so a later reuse of the same device
pointer can be treated as already released and leak the new allocation. Update
free_buffer, alongside the existing dev_to_host_ and block_ranges_ cleanup under
mapping_mutex_, to also erase release_ptr from released_allocations_ once the
release has been claimed and ops_.free_ has run; use claim_release_pointer and
free_buffer as the main symbols to locate the change.

---

Nitpick comments:
In `@src/a2a3/platform/shared/host/pmu_collector.cpp`:
- Around line 113-125: The init path in pmu_collector’s buffer distribution
silently ignores a failed BufferPoolManager::push_recycled call, unlike
BufferPoolManager::allocate_recycled_batch which falls back to
retire_unqueued_buffer when the recycled ring is full. Update the surplus-buffer
branch in the pmu_collector initialization logic to check the push_recycled
return value and, on failure, retire the buffer through the same fallback path
so it stays tracked/accounted for instead of being dropped from the pool for the
rest of the run.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d8a2622d-70be-442e-9c2b-cdc9dcdda093

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa7f26 and a712db9.

📒 Files selected for processing (21)
  • docs/dfx/args-dump.md
  • docs/dfx/dfx-buffer-capacity-audit.md
  • docs/dfx/l2-swimlane-profiling.md
  • docs/dfx/pmu-profiling.md
  • docs/profiling-framework.md
  • src/a2a3/platform/include/host/pmu_collector.h
  • src/a2a3/platform/shared/host/pmu_collector.cpp
  • src/a5/platform/include/host/pmu_collector.h
  • src/a5/platform/shared/host/pmu_collector.cpp
  • src/common/platform/include/host/buffer_pool_manager.h
  • src/common/platform/include/host/dep_gen_collector.h
  • src/common/platform/include/host/l2_swimlane_collector.h
  • src/common/platform/include/host/profiler_base.h
  • src/common/platform/include/host/scope_stats_collector.h
  • src/common/platform/include/host/tensor_dump_collector.h
  • src/common/platform/shared/host/dep_gen_collector.cpp
  • src/common/platform/shared/host/l2_swimlane_collector.cpp
  • src/common/platform/shared/host/scope_stats_collector.cpp
  • src/common/platform/shared/host/tensor_dump_collector.cpp
  • tests/ut/cpp/CMakeLists.txt
  • tests/ut/cpp/common/test_buffer_pool_manager.cpp

Comment thread src/common/platform/include/host/buffer_pool_manager.h
@zmnobug zmnobug force-pushed the issue-1251-spsc-buffer-pool branch from a712db9 to dc445ea Compare July 8, 2026 02:10
- Retire undelivered ready-full buffers instead of writing done from drain

- Make retired buffers use a mutex-protected exceptional holding pool

- Seed single-orchestrator profiler surplus buffers into the owner lane

- Add regression coverage and update profiling framework docs
@zmnobug zmnobug force-pushed the issue-1251-spsc-buffer-pool branch from dc445ea to 893b1f1 Compare July 8, 2026 04:49
@ChaoZheng109

Copy link
Copy Markdown
Collaborator

Re: issue #1251 子改动 #5(alloc off hot path, on replenish, batched + proactive)

已做到的部分:

  • ✅ 分配移出 drain 热路径:运行期 obtain_buffer 纯 pop,拿不到即返回 nullptr,不再 drain_done、不再 pop_recycled_any 跨 shard 借用、不再现场分配。
  • ✅ 块切分分配:allocate_recycled_batch 一次分配一块、注册一次、切成 N 个子 buffer,配合 range-based resolve_host_ptr,HAL 注册成本从"每 buffer"摊薄成"每块"。
  • ✅ 单线程分配无锁(启动期 proactive_replenish)。

没做到的部分:

  • ❌ "在 replenish 线程上、按水位(watermark)驱动主动分配"未实现。mgmt_replenish_loop 运行期只做 drain_done_into_recycled(),不看水位、不补块;所有分配集中在启动期,池启动后固定,运行期完全不分配。

issue #5 明确要求把 watermark 驱动的主动分配放到 replenish 线程上。请按 issue 要求补足这部分实现,或说明不实现的原因。

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

死代码:ProfilerAlgorithms::drain_done_for_shard

src/common/platform/include/host/profiler_base.h:397 新增的 drain_done_for_shard 全仓只有定义处一处引用,从未被调用——运行期回收走的是 mgmt_replenish_loop 里的 manager_.drain_done_into_recycled()

template <typename Mgr>
static size_t drain_done_for_shard(Mgr &mgr, int shard_index) {
    ...
}

建议删除,避免留下一个看似是运行期入口、实则从不执行的路径误导后续维护。

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

建议(增强,非阻塞):给 SpscRing 补一个并发内存序回归测试

现有 test_buffer_pool_manager.cppSpscRing 全是单线程用例(先全 push 再全 pop),唯一的多线程用例 WaitPopReadyWakesOnProducer 只 push 一次、测唤醒。而这个环的全部正确性都压在 push/pop 的 acquire/release 内存序上——单线程测试对内存序完全不敏感:以后有人把 head_.store(..., release) 手滑改成 relaxed、或删掉配套 barrier,单线程用例照样全绿,却会在弱内存序上产生静默乱序(消费者先看到 head 自增、后看到 entry 写入 → pop 出半写的垃圾指针)。

建议加一个回归护栏——注意仍是严格 SPSC(1 生产者线程 || 1 消费者线程,不制造多写竞争),只是让 push/pop 并发持续跑,校验 FIFO 顺序不乱、不丢、不重:

SpscRing<uint64_t, 1024> ring;
std::thread prod([&]{
    for (uint64_t i = 0; i < N; i++) while (!ring.push(i)) {}
});
uint64_t expect = 0;
while (expect < N) {
    uint64_t v;
    if (ring.pop(v)) { ASSERT_EQ(v, expect); ++expect; }  // 顺序必须严格递增
}
prod.join();

关键:CI 的 ut 跑在 x86(TSO 强内存序),即使内存序被放宽,x86 也几乎不乱序,纯压测基本抓不到。要成为确定性护栏,需在 TSan(-fsanitize=thread,建模 C++ 抽象机内存模型、arch 无关、relaxed 缺同步直接报 race)下跑,或在 aarch64 上跑。建议至少加 TSan 变体。

定位:仓库对 SPSC ring 的现有惯例是"单线程功能测 + onboard 验并发",本 PR 与之一致,所以这条不作为合入阻塞;但这是本 PR 唯一新引入的无锁原语,值得主动把"未来放宽内存序"这类静默退化挡在 CI 层,而不是等它变成线上偶发 hang。成本很低(上面几十行 + 一个 TSan 编译目标)。

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

播种 shard 与运行期消费 shard 错配(建议按 blockdim/cluster 分组)

问题:余量 buffer 的播种 shard 用 core % aicpu_thread_num,但运行期 process_entry 是按 shard_index = q(即 core 的 owner AICPU 线程)去 pop_recycled 补 free_queue 的。而 owner 由 assign_cores_to_threads() cluster 对齐分派(cluster ci → 线程 ci % N,每 cluster = 1 AIC + 2 AIV = PLATFORM_CORES_PER_BLOCKDIM 个 core)。两者不一致:core % N 把"每 cluster 3 个一簇"的不均匀摊平成按序号均分,和 cluster 轮询的不均匀对不上。

叠加本 PR 删掉了 pop_recycled_any(跨 shard 借用),错播的 buffer 无法被需要它的 lane 借到 → 忙 lane 欠货、闲 lane 囤货救不了场 → 预热期 free_queue 欠货、AICPU 丢样(第一圈 done→recycled 回流后自愈)。L2 的 AicpuTask 有 recycled_warm_target 掩盖,但 AicoreTask、PMU(warm_target=0)会实打实吃到

不需要序号严格对齐,只需数量匹配:recycled buffer 在同一 (shard, kind) 内可互换,所以只要每条 shard 的数量等于运行期该 lane 的 core 数即可。按 blockdim(=cluster)分组轮询就能保证这一点,且与 AIC/AIV 发现顺序无关:

  • 运行期 lane s 的 core 数 = PLATFORM_CORES_PER_BLOCKDIM × |{cluster ci : ci%N==s}|
  • 播种 shard = (i / PLATFORM_CORES_PER_BLOCKDIM) % N → shard s 的 core 数 = PLATFORM_CORES_PER_BLOCKDIM × |{group g : g%N==s}|
  • gci 同范围 [0, num_cores/PLATFORM_CORES_PER_BLOCKDIM) → 两者每个 s 的计数恒等 → 数量精确匹配

依赖的前提(num_cores == block_dim × PLATFORM_CORES_PER_BLOCKDIM,即每 cluster 严格 1 AIC + 2 AIV)正是 assign_cores_to_threads 本身就要求的拓扑,不新增假设

改法(L2 的 AicpuTask / AicoreTask 两处播种,PMU 同理):

// 播种 shard 必须与运行期 recycled 消费 shard 一致:runtime 按 cluster 对齐分派
// (cluster ci -> AICPU 线程 ci % N,每 cluster = PLATFORM_CORES_PER_BLOCKDIM 个 core)。
// recycled buffer 在 (shard,kind) 内可互换,故只需每条 shard 的“数量”匹配该 lane 的
// core 数,无需 core 序号严格对齐;按 blockdim 分组轮询即可让数量精确吻合(与 AIC/AIV
// 发现顺序无关)。跨 shard 借用(pop_recycled_any)已删,数量不匹配会导致预热期欠货丢样。
int shard = (aicpu_thread_num > 0) ? ((i / PLATFORM_CORES_PER_BLOCKDIM) % aicpu_thread_num) : i;

低危(仅预热期、自愈),不阻塞合入;但 AicoreTask/PMU 无 warm_target 兜底,建议一并修掉并加上上述注释。

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

recycled 环容量过配(建议按 blockdim 线程数平分 + 2x 余量,并加超容护栏)

问题:recycled_ 是二维 [shard][kind],但每一格都用同一个 kPoolQueueCapacity = kHostPoolQueueSize 声明:

using DoneRing     = SpscRing<DoneInfo, kPoolQueueCapacity>;   // 920
using RecycledRing = SpscRing<void *,  kPoolQueueCapacity>;    // 920

这个 920(L2)是各 kind buffer 数之和,对 done_shards_ 是对的(done 环混装所有 kind,必须取和);但 recycled_[shard][kind] 每格只装一种 kind,而且只对应一条 lane 的份额,根本到不了 920:

recycled 格子 该 kind 全量 现容量 真实上界(单 lane 份额)
AicpuTask 576 920 72×8/线程数
AicoreTask 288 920 72×4/线程数
SchedPhase 24 920 4×6/线程数
OrchPhase 32 920 4×8/线程数

单个 L2 子系统 manager 里 recycled 现约 115 KB,其中绝大部分是过配(SchedPhase 环需 ≤24 却开 920,过配约 38×)。

根因:一个常量(为 done 算的"各 kind 之和")被同时套在了"每格只装单 kind、且只占一条 lane 份额"的 recycled 环上。

当前结论(增量修,动态化留后续):线程/shard 数当前是编译期固定 PLATFORM_MAX_AICPU_THREADS=4(start()constexpr kDrainThreads/kCollectorThreads),所以先按固定 4 平分 + 2x 余量给 recycled 单独定容:

// recycled 每格只装单 kind、只占一条 lane 份额;按 PLATFORM_MAX_AICPU_THREADS 平分 + 2x 余量。
// (done 仍用“各 kind 之和”,因为它混装所有 kind。)运行期按实际 aicpu_thread_num 动态定容待后续统一改造。
constexpr size_t kRecycledCapacity =
    PLATFORM_MAX_CORES * PLATFORM_PROF_BUFFERS_PER_CORE / PLATFORM_MAX_AICPU_THREADS * 2;  // AicpuTask=288
using RecycledRing = SpscRing<void *, kRecycledCapacity>;

安全前提 + 护栏:/4 定容成立的前提是"每条活跃 lane 管的 core ≤ MAX_CORES/4",即运行期 aicpu_thread_num 恒为 4(或 core 随线程同比)。核多线程少(如 aicpu_thread_num<4 而 core 不减)时单 shard 会超容 → push_recycled 失败 → retire → 静默缩池。因为播种时已知 num_cores/aicpu_thread_num,建议加一句超容即报错,把静默退化变成明确失败:

if (per_shard_seeded_count > kRecycledCapacity) {
    LOG_ERROR("recycled ring under-provisioned: shard=%d kind=%d need=%d cap=%zu "
              "(aicpu_thread_num=%d) — switch to runtime-sized rings", ...);
}

低危、不阻塞;省内存(L2 recycled 115KB→约 36KB)+ 语义更清晰(done 取和 / recycled 取单 lane 份额),护栏保证未来"核多线程少"配置不会静默丢样。

- Add runtime recycled-lane watermark replenishment while keeping drain free_queue publication allocation-free
- Split done and recycled host ring capacities for per-kind lane sizing
- Align L2 and PMU recycled seeding with cluster-owner shards
- Add SPSC concurrency and replenish capacity regression tests
- Update profiling documentation to describe the new replenish contract
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Code Health] Redesign profiling buffer-pool as an all-SPSC lock-free per-lane pipeline (replenish-driven alloc/recycle)

2 participants