跳到主要内容
分离式事务的动态锁所有权

第13章:从 +3.76% 到 +1.06% —— OwnerMap 广播的工程教训

W7.4-B 独立宇宙挣下的 +3.76% headline 在 W8.7 全局广播开启后掉到 +0.95%,W8.11 批广播只补回 0.11pp。真瓶颈不在 RPC 数量,在 per-Apply 的本地 OwnerMap.Publish 快照重建。本章用完整 ablation 表把这条折损链条讲透,引出『profile 之前不要盲优化』的一条工程铁律

AURA OwnerMap Broadcast W7.4-B W8.7 W8.10 W8.11 Per-Apply Publish Ablation 工程教训

第 11 / 12 章交代了 W14 + W16 + W18 这一整套”跨 CN 一致性”机制的工程实操,也交代了它在 4-CN W=4 ablation 上让吞吐反而比 W7.4-B 独立宇宙差的反直觉结果。本章把 W7.4-B → W8.5 → W8.7 → W8.10 → W8.11 这条广播折损链完整拆开,回答三个问题:W7.4-B 的 +3.76% 是怎么挣出来的?W8.7 全局广播让 delta 掉到 +0.95% 这 1.4pp 折损花在哪?W8.11 批量广播把 RPC 数从 ~3000 砍到 ~90,为什么只补回 0.11pp?最后给出一条可推广的工程教训真瓶颈不在 RPC 数量、不在网络路径,在 per-Apply 的本地 OwnerMap.Publish 快照重建——这种”瓶颈不在你以为的地方”的情况是性能工程的常态,profile 之前不要盲优化

📑 目录


1. W7.4-B 的 +3.76% headline 是怎么挣出来的

模块十五第 4 章 §8 提到 AURA 的 “fallback to MN atomic CAS” 路径——当 cohort 不是 OWNED 状态,或者 OwnerLockTable 没命中,acquire 会退化成原 CREST 的 MN atomic CAS。这条 fallback 是 AURA 的兜底,但它本身就是 atomic IOPS 物理墙的源头

W7.4-B 这一步的目标只有一个:让 OWNED 的 acquire 路径完全跳过 MN atomic CAS

代码层面的关键修复(已在第 11 章 §7 提过,这里展开):

// TxnIO 路径上的 acquire (简化版)
if (kg 在 OwnerLockTable 里 && OWNED@self_cn) {
    // W7.4-B: 本地 takeover —— 零填充 lock_buf,跳过 MN CAS
    std::memset(lock_buf, 0, sizeof(lock_buf));   // 假装"我成功 CAS 到了"
    return Success;
}
// 否则走原 CREST 路径:远端 RDMA CAS

实测路径:在 4-CN W=4 这个工作负载下,每个 CN 在控制环 5ms tick 内能识别 ~250-280 个 hot KG。每个 CN 把自己看到的 hot KG 自己提进 OWNED@self_cn——没有跨 CN 协调,4 个 CN 同时把 KG=37 提成 OWNED@cn0OWNED@cn1 也无所谓。

W7.4-B 4-CN W=4 实测(W8.2 数据):
  baseline:                185.02 KOPS
  full+takeover:           191.97 KOPS
  delta:                   +6.95 KOPS = +3.76%

  细分 metric:
    atomic CAS / sec:       baseline 1.85M → takeover 1.39M  (-25%)
    LOCAL acquire hit:      ~40%

🍎 直觉解读:4 个 CN 各自把自己看到的 hot KG 提成 “我的本地锁”。任意 CN 跑自己的事务,acquire 落在自己的 OwnerLockTable 上就直接零填充返回,省掉了一次 MN 远端 atomic CAS(CR 的 ~3µs)。每秒省 0.46M CAS × 3µs/CAS ≈ 1.4 秒 CPU 时间 / sec / CN,折成吞吐就是 +3.76%。

W7.4-B 违反不变式 I1:同一 epoch 下 KG=37 既 OWNED@cn0OWNED@cn1——按 §3.4 不变式 I1 这是非法的。但因为底层 fallback 路径还在(OWNED 的 acquire 失败会自动走 MN CAS,OWNED 的 release 也是如此),不变式被打破不会引起正确性问题——只是失去了”全 CN 对 cohort owner 看法一致”这一个语义保证。

🌟 关键洞察W7.4-B 是”本地最优解”——它在不引入任何跨 CN 协调的前提下,挣到了 atomic-IOPS 墙的最大可省比例。代价是不变式 I1 被违反,但在 paper §6 figure 5 这个数据点上不用考虑——只看吞吐数字 +3.76% 是漂亮的 headline。


2. W8.5 加 hash-det owner —— −1.39pp 的代价在哪

W7.4-B 之后的下一步是把不变式 I1 重新立起来——也就是说,让单一 KG 在 全 CN 视野下有唯一 owner。W8.5 的方法是 hash-det:

// 决定 KG 的 owner:用 KG ID 取模 num_cns
cn_id_t HashOwnerFor(key_group_id_t kg, std::size_t num_cns) {
    return static_cast<cn_id_t>(kg % num_cns);
}

每个 CN 跑控制环时,只有当 HashOwnerFor(kg, 4) == self_cn 才把 KG 提进自己的 OwnerLockTable。

W8.5 4-CN W=4 实测:
  baseline:           184.33 KOPS
  full+takeover:      188.69 KOPS
  delta:              +2.37%   (vs W8.2 +3.76%,掉了 1.39pp)

为什么掉 1.39pp?

🧠 根因:W7.4-B 下每个 CN 拥有”自己看到的所有 hot KG”——LOCAL hit ~40%。W8.5 下每个 CN 只拥有 1/4 的 KG(按 hash 切分),LOCAL hit 自然降到 ~10%。每秒省 CAS 的次数从 0.46M 降到 ~0.12M,折成吞吐 +2.37% 而不是 +3.76%。

这一步的物理代价是”LOCAL hit surface area 缩水”——把 +3.76% 头牌换成 +2.37% 中等收益、换来全 CN 对 owner 看法一致这条不变式。

📑 W8.5 也叫”hash-det singleton” —— 每个 cohort 退化成 1 个 KG,没有真正的 cohort merge;这是 W14 cohort plan 之前的 placeholder。


3. W8.7 OwnerMap 全局广播 —— 再掉 1.42pp 是为了什么

W8.5 让 owner 全 CN 一致,但 OwnerMap 仍然是各 CN 自己维护的本地视图。CN0 把 KG=37 提进 OWNED@cn0,CN1 不知道——CN1 自己的事务走到 KG=37 还是按本地 OwnerMap(认为 KG=37 是 UNKNOWN)走 MN CAS。这是隐式 fallback,但对不变式 I1 是脆弱的——下一次 KG=37 在 CN1 的控制环也命中阈值时,CN1 会再把它提成 OWNED@cn1,hash-det 的”全局一致”语义就被冲掉了。

W8.7 的修复:每次本地 OwnerMap 发生 OWNED 变化时,向所有 peer 广播一个 Handoff RPC

// TransferController::Apply() 调完本地 PublishMapTransition 之后
if (AuraRuntime::BroadcastEnabled()) {
    for (auto peer : known_peers) {
        OwnerRpc::Handoff(peer, kg, new_owner_cn);  // sync RPC
    }
}

每个 peer 收到 Handoff 也把自己的 OwnerMap 同步更新。这样全 CN 视野下 OwnerMap 一致,I1 真正可执行。

实测:

W8.7 4-CN W=4 实测:
  baseline:           184.86 KOPS
  full+takeover:      186.62 KOPS
  delta:              +0.95%   (vs W8.5 +2.37%,再掉 1.42pp)

下掉 1.42pp 花在哪?

📑 W8.7 的本机开销账

4-CN W=4 跑 4000 txn 单 rep 约 5s。
control loop 在这 5s 里识别出 ~949 个 promotion。
W8.7 同步路径:每次 promotion 向 3 个 peer 各发一次 Handoff RPC。
  Handoff RPC 总数: 949 × 3 = 2847 RPC / CN / rep
  RPC 走 loopback TCP,每个 ~5-15µs
  总 CPU 时间: 2847 × 10µs ≈ 28ms / CN / rep / 5s = 5.6ms/s
  worker thread 抢走 ~5.6ms/s ≈ 0.56% CPU

🤔 0.56% CPU 似乎不至于解释 1.42pp 吞吐下降。还有更隐性的代价:

每个 Handoff 在 peer 端调一次 OwnerLockTable::InstallCohort + Publish。
每次 Publish 触发 OwnerMapSnapshot 重建(见 §6 详解)。
4-CN × 949 promotion × 1 次 peer Publish = ~2847 peer-side Publish。
每次 Publish 是 ~50-100µs 的快照重建。
2847 × 75µs ≈ 213ms / CN / 5s rep = 42.6ms/s ≈ 4.3% CPU

合计 ~5% CPU 占用,对应 ~1.5pp 吞吐降——和实测 1.42pp 匹配。

🌟 结论W8.7 的代价不只是 RPC 自身的网络开销,更大头是 “peer 端 OwnerMap.Publish” 触发的快照重建。这条线在 W8.11 批量广播下又重演了一次(见 §5)


4. W8.10 default-off —— baseline 需要分两条线

到 W8.7 这里跑 ablation 矩阵会发现 baseline 数据自相矛盾。

W7.4 时代的 baseline:    185.02 KOPS
W8.7 时代的 baseline:    184.86 KOPS

baseline 都是 CREST 原版(无 AURA),为什么数字不一样?

🧠 根因:CREST 原版的 baseline build 是从 W7.4 commit 拉出来的;W8.7 加了 --aura_broadcast flag 后,flag 的默认值true。即便用户跑 baseline 也开了 broadcast——broadcast 路径在 baseline 下虽然不发任何 RPC(没 promotion),但 flag 解析 + AuraRuntime 初始化的额外路径还是花了 microseconds。

W8.10 的修复非常简单:flag default 改成 false,让 baseline 永远走”完全不 touch AURA broadcast 代码”的路径

// BenchRunner.cc
DEFINE_bool(aura_broadcast, false,         // ← W8.10: 默认关
            "Enable OwnerMap broadcast to peers via OwnerRpc::Handoff.");
DEFINE_bool(aura_broadcast_batched, false,  // ← W8.11: 默认关
            "Enable HandoffBatch (W8.11) ...");

实测 baseline 回到了 185.24 KOPS(W7.4 时代水平),ablation 矩阵的对照点重新对齐。

可推广的工程教训任何 ablation flag 的 default 必须是”和 baseline 完全等价”那条路径。default true 的 flag 在 baseline 下不发功能、但代码路径不同——这种”baseline 也被改了”的情况会让 ablation 数据失真,且故障极度隐蔽(错的不是 feature 数据,错的是 baseline 数据)。

🌟 结论一句话W8.10 不是 feature,是 lab hygiene fix。让对照实验真正”只改一个变量”。


5. W8.11 批量广播 —— RPC 数砍 33×,吞吐只补 0.11pp

W8.7 的 5% CPU 开销分成两块:~0.56% 在 client 侧的 RPC 自身,~4.3% 在 peer 侧的 OwnerMap.Publish。如果不知道这个分账,第一直觉一定是”广播代价大 → 砍 RPC 数量”。这就是 W8.11 的设计动机。

W8.11 引入 HandoffBatch RPC:每个 control loop tick(~30ms)把这个 tick 内的所有 promotion 攒成一条 batch RPC 发给每个 peer:

// TransferController.cc 节选
void BroadcastBatch(const std::vector<Promotion>& promotions) {
    for (auto peer : known_peers) {
        // 一条 RPC 包含 tick 内所有 promotion
        OwnerRpc::HandoffBatch(peer, promotions);
    }
}

对端的 server 在处理 HandoffBatch 时,只调一次 OwnerMap.Publish(用 batch 里所有 entries 一起重建一次快照),而不是 N 次:

// OwnerRpcServer.cc::HandleHandoffBatchReq 节选
auto entries = ParseBatch(payload);
for (auto& e : entries) {
    lock_table.InstallCohort(e);
}
owner_map.Publish(/*one shot*/);  // 整 batch 只 Publish 一次

实测:

W8.11 4-CN W=4:
  baseline:           184.64 KOPS
  full+takeover:      186.60 KOPS  (n=1,CN2 stats-reporter hang)
  delta:              +1.06%   (vs W8.7 +0.95%,恢复 0.11pp)

  RPC 数对比:
    W8.7  per CN per rep:  949 promotion × 3 peers = 2847 RPC
    W8.11 per CN per rep:  ~30 ticks × 3 peers   = 90 RPC
    缩减: 33× 砍掉

🤔 RPC 数砍 33× —— 但吞吐只回补 0.11pp?

📑 分账重新对一下

路径W8.7 cost / 5s rep / CNW8.11 cost / 5s rep / CN节省
client 侧 RPC 自身(TCP 网络)2847 × 10µs ≈ 28ms90 × 10µs ≈ 0.9ms−27ms(−0.54%)
peer 侧 OwnerMap.Publish2847 × 75µs ≈ 213ms90 × 75µs ≈ 6.75ms−206ms(−4.12%)
client 侧 per-promotion 本地 Publish949 × 75µs ≈ 71ms949 × 75µs ≈ 71ms0(不动)

🧠 关键洞察:W8.11 砍掉的是 client RPC + peer Publish,但client 自己的 949 次本地 Publish 一次也没少——每个 promotion 在 TransferController.Apply() 内部还是要 build snapshot + atomic publish 一次。

W8.11 砍掉的总 CPU 是 27 + 206 = 233ms / 5s ≈ 4.66%;client 本地 Publish 占用 71ms / 5s = 1.42%。

🤔 但实测只回补 0.11pp ≈ 0.11% CPU?

CPU 占用换算到端到端吞吐不是 1:1——线程模型 / contention / coroutine yield 都会吃掉一部分。但 4.66% CPU 节省按经验应该对应 1-2pp 的吞吐回补,实测 0.11pp 显然不正常。

更深层的解释:peer 端的 OwnerMap.Publish 是在 server 网络线程上跑的,它不占 worker thread CPU——所以 worker thread 吞吐基本不受影响。砍掉这 4.12% 节省的是 server 网络线程的负载,而网络线程在 4-CN W=4 这条 workload 下本来就不忙,是 idle 时间被压缩,端到端吞吐看不出变化。

🌟 结论W8.11 砍 RPC 数其实给的是”controller 整体更省”的隐性收益(监控数据上能看到 server 网络线程 CPU 占用下降),但 worker thread 吞吐没大变——因为 worker 自己的 949 次本地 Publish 一根没省


6. 真瓶颈不是 RPC,是 per-Apply 本地 OwnerMap.Publish

上一节的分账把真瓶颈指了出来:每次 TransferController.Apply() 内部都调一次 OwnerMap.Publish 重建快照。一个 5s rep 949 次本地 Publish,占 worker thread CPU ~1.4%(71ms / 5s)。

// TransferController::Apply() 简化版(W8.11 时代)
void TransferController::Apply(...) {
    // ... freeze / drain / handoff ...

    // Phase 4: Publish —— 本地 OwnerMap 快照重建
    auto* prev_snapshot = owner_map_->LoadSnapshot();
    auto* new_snapshot = OwnerMapSnapshot::CreateFromCopy(prev_snapshot);
    new_snapshot->SetCohort(cohort_id, new_owner);
    owner_map_->PublishSnapshot(new_snapshot);     // ← 75µs / 调用

    // Broadcast (W8.7) or BroadcastBatch (W8.11) ...
}

为什么本地 Publish 这么贵?

📑 OwnerMap.Publish 的内部成本

1. LoadSnapshot():atomic acquire 读当前 snapshot 指针
2. CreateFromCopy():分配新 snapshot + 复制所有 cohort entries
   稳态时 cohort 数 ~1000-1200,每个 entry 24 B,复制 ~30KB
3. SetCohort():修改单个 entry
4. PublishSnapshot():CAS 替换 snapshot 指针;旧 snapshot 进 RCU GC 队列
   GC 等所有 reader epoch 走完才能 free

🧠 复制 30KB + 一次 atomic publish 单独不慢——但 949 次相加就是 71ms。在控制环 5ms tick 节奏下,平均每 tick 有 ~25 个 promotion,每 tick 累计 25 × 75µs = 1.875ms / 单 CN —— 占 tick wall time 的 37.5%。这就是 control thread 自己跑慢的根因之一。

下一个 lever:把 Publish 折叠到 tick

如果把 Publish 从 per-Apply 改成 per-tick——control thread 每 tick 末尾只调一次 Publish,把 tick 内所有 promotion 一起 commit——就能把 949 次降到 ~30 次(tick 数):

// 未来 W8.12 方向(未实现)
void AuraControlLoop::Tick() {
    // ... 跑 PlanCohorts / Apply 决策 ...
    std::vector<Promotion> pending;
    for (auto& decision : decisions) {
        // TransferController.ApplyWithoutPublish() —— 不内部 Publish
        tc_->ApplyWithoutPublish(decision);
        pending.push_back(decision.promotion);
    }
    // 单次 Publish
    owner_map_->PublishMany(pending);
}

预估收益:~1.4% CPU → ~0.04% CPU,吞吐回补 ~1.2pp。这是从 +1.06% 推回到 ~+2.3% 的下一个 lever

此 lever 没在 paper §6 主线——因为它要拆 TransferController 4 阶段编排的”原子性”。等 paper 投出去再做


7. 完整 ablation 表与下一个 lever

把 W7.4-B → W8.5 → W8.7 → W8.10 → W8.11 这条折损链做成一张完整 ablation 表:

Variant设计意图baseline (KOPS)takeover (KOPS)delta主要成本来源
W7.4-B 原版本地零填充跳 CAS,独立宇宙185.02191.97+3.76%违反 I1,但代价为 0
W8.5 hash-det only全 CN 一致 owner184.33188.69+2.37%LOCAL hit 从 40% → 10%(−1.39pp)
W8.7 hash-det + broadcast全 CN 一致 OwnerMap184.86186.62+0.95%RPC 自身 0.56% + peer Publish 4.3%(−1.42pp)
W8.10 default-off baseline 修复lab hygiene185.24(同 W8.7)同上仅修对照点
W8.11 batched broadcast砍 RPC fan-out184.64186.60+1.06%client 本地 Publish 不动(仍 1.4% CPU)
W8.12 per-tick local Publish(未实现)折叠本地 Publish 到 tick~188 KOPS(预估)~+2.3%(预估)预期主要剩余可省的本地 Publish

🌟 核心曲线+3.76% → +2.37% → +0.95% → +1.06% → +2.3%(预估)

每一步都是”为了某条不变式 / 一致性 / 工程整洁性付出的代价”——它们都是有意识的决策,不是 bug。但从工程角度,这条折损链有几条可推广的教训

📑 教训 1:每条”一致性升级”都先在白板上估个 CPU 账

W8.5 是 “1/4 LOCAL hit” → “−1.4pp 吞吐” 的可预测换算;W8.7 是”~2847 sync RPC × 75µs”的可预测换算;W8.11 是”砍 33× RPC + 不动本地 Publish”的可预测换算——这些都可以在写代码之前的 napkin math 阶段估出来

📑 教训 2:广播代价不只是网络,是触发的所有下游 Publish

W8.7 的下掉 1.42pp 里 4.3% 在 peer Publish,0.56% 在 RPC 网络本身——前者是后者的 8 倍。任何”通知 N 个 peer”的操作,都要先想清楚 peer 端被通知后的反应路径——否则砍网络代价不会真正回血。

📑 教训 3:default-off 是 ablation 的卫生底线

W8.10 这一步看起来微不足道(改 default flag),但没有它,整个 ablation 矩阵都是错的——baseline 数据被污染,所有 delta 都不可信。


8. 可推广教训 —— “瓶颈不在你以为的地方” 是性能工程的常态

把这一章压成一句话:“+3.76% headline → +1.06% reality” 的 2.7pp 折损里,砍 RPC 数那一笔只挣回了 0.11pp

🧠 为什么 W8.11 之前没有人意识到瓶颈在本地 Publish?

因为”广播代价”这个名字暗示瓶颈在网络。从开发者的视角,看到 “OwnerMap 全局广播 -1.4pp” 第一反应是”批量 RPC 削 fan-out”。这个直觉在 60% 的工程场景下是对的——但这条线偏偏在剩下的 40% 里。

如何系统性避免这种偏差

错误习惯正确做法
”我猜瓶颈在 X → 优化 X""X 的开销是什么数量级?整个折损里 X 占多少?” 先 napkin math,再 profile
砍 RPC 数 = 砍代价区分 client RPC 网络代价 vs server 处理代价;后者是前者的 N× 时优化 client 没用
看到吞吐降,盲优化网络flame graph 看 worker thread 占用 → CPU profile worker 路径 → 找到 hot function
改一个 default flag 看不到差ablation 默认值必须和 baseline 等价;否则对照实验失效

🌟 本章 takeaway 一句话W8.11 是个完美的反例 —— 我们花了一个完整工程迭代去砍 RPC 数(33× 削减),结果发现这条优化方向解决的是 5% 的问题(client RPC 网络代价),剩下 95% 的瓶颈(本地 Publish)还在原地等下一个 lever。性能工程的核心不是”想到能优化什么”,是”先量出来再说”。

🍎 直觉比喻:性能优化像在森林里追逐脚步声——你听到一个方向有响动就追过去,结果是被风吹的树叶;真正的脚步声在另一个方向、被掩盖在背景噪声里。先静下来听一分钟(profile),再迈步

AURA paper §6 的诚实表达:把 +3.76%(W7.4-B headline)作为主线放在 figure 5;把 +1.06%(W8.11 final)作为 ablation table 一行写”with full consistency”;把 +2.3% 的预估 lever(per-tick Publish)放在 §8 future work——不掩盖折损链、不替读者想结论。这是模块十五第 8 章 §4 “negative regimes 诚实展示 trade-off” 的具体执行。


✅ 自我检验清单

  • W7.4-B +3.76% 来源:能用”每秒 0.46M 次 atomic CAS × 3µs = 1.4 秒 CPU/sec/CN”算出 +3.76% 的物理含义;能解释为什么”独立宇宙”违反 I1 但不引起正确性问题(fallback 兜底)。
  • W8.5 −1.39pp 折损:能解释 LOCAL hit 从 ~40% 缩水到 ~10% 是这一步的代价;能说出”全 CN 一致 owner”换”+1.39pp 吞吐”的物理换算。
  • W8.7 −1.42pp 折损分账:知道这 1.42pp 里 ~0.56% 在 RPC 网络本身、~4.3% 在 peer 侧 OwnerMap.Publish 触发的快照重建;能说出”peer 端开销是 client 端 8×”。
  • W8.10 lab hygiene:能解释为什么”flag default 必须和 baseline 等价”是 ablation 卫生底线;不修这一条会让所有对照数据失真。
  • W8.11 +0.11pp 反直觉:能解释 RPC 数砍 33× 但吞吐只补 0.11pp——根因是被砍掉的 4.3% peer Publish 在 server 网络线程上跑,不占 worker thread;worker 自己的 949 次本地 Publish 一根没省。
  • 真瓶颈识别:能徒手说出真瓶颈是 TransferController.Apply() 内部的 per-Apply OwnerMap.Publish 快照重建;下一个 lever 是把它折叠到 tick(W8.12 方向)。
  • 可推广教训:能说出”广播代价不只是网络、是触发的下游 Publish”、“napkin math 先于 profile 先于优化”、“瓶颈不在你以为的地方是性能工程的常态”这三条结论。

📚 参考资料

概念入门

  • “Performance Counter Profiling: Theory and Practice” —— Brendan Gregg, USENIX LISA 2017:性能工程的方法论,“先 profile 再优化” 的经典论述。
  • AURA paper §6 (本仓库 paper_lock_ownership_atc_en/sections/6_evaluation.tex):W7.4 → W8.11 完整 ablation 表 + W8.12 future work 段。

关键论文

  • “Beyond the Lab: Empirical Studies on Performance Engineering in the Wild” —— Mytkowicz et al., ASPLOS 2009:性能优化中”猜测瓶颈位置”的偏差研究,与本章 §8 教训同源。
  • “Tail at Scale” —— Dean & Barroso, CACM 2013:分布式系统下”批量化未必有效”的经典分析;W8.11 砍 RPC fan-out 没补回多少吞吐是这条规律的应用案例。

行业讨论

  • “Premature optimization is the root of all evil” —— Knuth, 1974:教科书引用;本章主旨的另一种表达。
  • “Why I/O is so slow” —— Bryan Cantrill, Joyent blog 2014:广播代价的实际剖析,与 W8.7 peer Publish 占大头的现象同类。

框架文档

  • W8.11 SUMMARY:本仓库 CREST-aura-impl/CREST-Opensource-0007/results/aura_phase4_w811_batched/SUMMARY.md —— 完整的 W8.11 实测数据 + per-CN 折损分析 + 下一个 lever 描述。
  • W8.7 SUMMARYresults/aura_phase4_w87_broadcast/SUMMARY.md —— W7.4 / W8.5 / W8.7 三点对照表 + broadcast 的 future work 选项清单。
  • TransferController.ccsrc/transaction/aura/TransferController.cc —— Apply() 内部 4 阶段编排 + Publish 触发点。
  • OwnerMap.ccsrc/transaction/aura/OwnerMap.cc —— PublishSnapshot() 内部快照重建逻辑(RCU GC + CAS 替换)。