diff --git a/research/HANDOFF.md b/research/HANDOFF.md index c58d27b..bdb281c 100644 --- a/research/HANDOFF.md +++ b/research/HANDOFF.md @@ -1683,25 +1683,54 @@ EMA/MACD/ATR 是递推的、BB/SMA/量比是窗口的,理论上都能 O(1) 更 出来的。换外生指标后相关降到 0.271。**凡是用被解释变量去构造解释变量的,先 怀疑循环。** -注入合成负载复现(离线同一窗口,2 核): +注入合成负载复现(离线,2 核): -| 背景 | `compute` 增量 | 放大 | +| 背景 | `compute` | 放大 | |---|---|---| -| 空闲 | **29.9ms** | 1.00x | +| 空闲 | 29.9ms | 1.00x | | 1 个满载进程 | 41.7ms | 1.39x | | 2 个满载进程 | 80.2ms | 2.68x | | 3 个满载进程 | 85.4ms | 2.86x | -采集器实测:密集度=1 时 67ms(2.24x)、总体中位 100ms(3.34x)。落在「1~2 个 -满载进程」到「2 个以上」之间,正是 **2 核上跑 主进程 + 2 worker** 该有的样子 -(主进程常驻负载不小:10 路 K 线 WS + 10 路盘口 + 10Hz 采样)。 +⚠️ **别拿这张表算争抢倍数——那个 29.9ms 基线是废的。** 它每次喂同一个窗口, +`append_bar` 发现没有新根、一根没追(实测 6 次调用 stream 长度 2001→2001 零 +增长),测到的只是信号链本身。拿它去比在场的 `inner_ms` 得出「3.3 倍争抢」, +**又是一次口径不对齐——和被撤回的 22/86 同一类错,隔了一小时又犯一次。** -**结论与代价:** `inner_ms` 100ms = **约 30ms 真实计算 × 3.3 倍争抢**。 -剩下的延迟不是算法性质的,继续压引擎收益有限(把 30ms 砍一半只换回 15ms×3.3 -≈ 50ms,且越往下越难)。真正的杠杆是**核数**或**减币**。 +口径对齐后:**在场每次调用平均追加 2.81 根**,因为 2 个 worker 各存一份缓存, +各自漏掉对方处理过的根(`stream_bars` 逐根 delta 在 ±1~±4 对称分布,正是两个 +独立计数器的指纹;**真重建只占 0.31%**,此前算的 21.5% 是把换 worker 的小幅 +回退误判成重建)。而 `add_indicators` 每次追加都全表重算,所以追加成本随根数 +近线性涨: + +| 离线每次追加 | 1 根 | 2 根 | 3 根 | 4 根 | +|---|---|---|---|---| +| `compute` | **45.0ms** | 55.7ms | 70.1ms | 85.9ms | + +在场平均 2.81 根 → 离线插值 **67.3ms**;在场 `inner_ms` 中位 **100ms**。 + +**结论(已按口径修正):** `inner_ms` 100ms 拆成 + +| 档 | 耗时 | 性质 | +|---|---|---| +| 信号链 + `_rebuild` | ~30ms | 真实计算,追 0 根时的地板 | +| 追加 2.81 根(含 5m 腿) | ~37ms | **其中约 22ms 是缓存重复的浪费** | +| 2 核争抢 | ~33ms | **1.49x**,不是 3.3x | + +⚠️ 争抢是 1.5 倍而非 3.3 倍,所以「加核」的收益比上一版写的小得多。 + +**新发现的便宜杠杆:按币绑定 worker。** 现在 symbol 落到哪个 worker 是随机的, +每个 worker 的缓存都只见到自己处理过的根,于是人人都要追 2.81 根。若把每个 +symbol 固定到同一个 worker(symbol hash → worker),每次只追 1 根:离线 +67.3 → 45.0ms,在场约省 22ms × 1.49 ≈ **33ms/币,占 inner_ms 三分之一**。 +代价是失去负载均衡,但十币两 worker 本来就是静态五五分。 + +`add_indicators` 增量化(§5.71 末尾那条未做项)会同时压掉追加档的大头, +两件事叠加。 ⚠️ 这和 §5.7 心跳里「币数 > 核数时加 worker 无用」不矛盾但要分清:**加 worker -确实无用(worker 已等于核数),加核有用。** 两者在原判定里被混成一句了。 +确实无用(worker 已等于核数);加核有用但只值 1.5 倍那部分。** 原判定把两者 +混成一句了。 --- diff --git a/research/live/shadow_hb.py b/research/live/shadow_hb.py index 1f1a6a7..f31030b 100644 --- a/research/live/shadow_hb.py +++ b/research/live/shadow_hb.py @@ -874,13 +874,13 @@ class Shadow: if not INCR_ON: return (f"币数 {len(SYMS)} > 核数 {CORES},加 worker 无用(CPU 密集)" f"。压单币耗时 → 开 SHADOW_INCR=1 走增量(实测 3.56x)") - # 增量已生效时,单币耗时的大头不再是算法而是争抢:离线同一窗口 30ms, - # 在场 100ms,注入合成负载能复现这个倍数(HANDOFF §5.72)。所以这里不能 - # 再推荐「继续改算法」——那是按 3.3 倍虚高的数去优化 30ms 的真实计算 + # 增量已生效时(§5.72 口径对齐后):单币 100ms ≈ 信号链 30ms + 追加 + # 2.81 根 37ms + 争抢 33ms。争抢只有 1.49x,加核收益有限;而追加那 37ms + # 里约 22ms 纯属浪费——symbol 随机落 worker,各缓存都漏掉对方处理的根 return (f"币数 {len(SYMS)} > 核数 {CORES},增量已生效,加 worker 无用" - f"(worker 已等于核数)。单币 {i:.0f}ms 里真实计算约 30ms," - f"其余是 {CORES} 核上争抢的放大 —— 杠杆是加核或减币," - f"不是继续压算法") + f"(worker 已等于核数)。单币 {i:.0f}ms 里争抢只占约 1.5x," + f"最便宜的一刀是按币绑定 worker(每次只追 1 根,省约三分之一)," + f"其次是 add_indicators 增量化") async def run(self) -> None: await self.start()