更正上一提交:争抢是 1.5x 不是 3.3x,并发现按币绑 worker 可省三分之一
上一提交的 3.3x 基线是废的:离线每次喂同一窗口,append_bar 一根没追 (实测 6 次调用 stream 2001→2001 零增长),测的只是信号链地板 30ms, 拿它比在场 inner_ms 又是一次口径不对齐——和刚撤回的 22/86 同一类错。 对齐后:在场每次平均追 2.81 根(2 worker 各存一份缓存、各自漏掉对方 处理过的根),离线同口径 67.3ms,在场 100ms → 争抢 1.49x。 顺带修正「重建占 21.5%」:真重建只有 0.31%,此前把换 worker 的小幅 回退误判成重建。 新杠杆:symbol 固定到同一 worker,每次只追 1 根,离线 67.3 → 45.0ms, 在场约省 33ms/币(inner_ms 三分之一)。心跳判定改指这一刀。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+39
-10
@@ -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 倍那部分。** 原判定把两者
|
||||
混成一句了。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user