Files
Chan/research/live/probe_inner.py
T
jackyu66gitandCursor bfdb2f2e2a 引擎四处「算了没人要的东西」,append_bar 13.9ms → 6.6ms
服务端把增量上线后回传两个热点:add_indicators 为加一根重算全表(占 34%)、
cal_bi_list 整表重扫(51%)。顺着查下来四处都不是算法慢,是算了没人读的结果。

1. cal_trend 挂到 lean 下。它不是笔的依赖(bi.py:221 在它自己的循环里读自身
   序列状态),服务端 verify_incr_parity 三币 1800 根已对拍定论。web 走非
   lean,klc_trend 图层不受影响。

2. check_fx_pattern 删掉拼完就丢的字符串。它把 klu.to_string() 拼成 p 只为
   一行注释掉的 print——2000 根上近 3 万次 f-string 加 6 万次 enum 格式化,
   而且在 cal_bi_list 内层。klu.pattern 只被 cal_klu_pattern 自己的双K/三K
   判定读,不出模块不进 web,所以整个调用在 lean 下也跳过。

3. ChanBI.add_klc 去二次方。去重原本线性扫 klc_list,且每加一根就把整笔所有
   KLU 的 macdhist 重累一遍,往一笔加 k 根是 O(k²)。改成下标集合加
   macd_hist/macd_div 惰性求值。这两个值只有背驰判定(bsp.py)读,lean 下
   bsp 根本不算。

4. add_indicators 批量挂列。2001 行上 TA 计算合计只有 2.5ms,而 30 多次
   df['x']= 要 3.6ms——开销大头是 BlockManager 逐列插入不是计算,改为一次
   concat。cal_volume_ratio 里为算一列 rolling 而 copy() 整张 40 列表,一并去掉。

实测(本机,2001 根窗口。服务端基线 21.8ms 是另一台机器,别直接比绝对值):
  append_bar        13.9 → 6.6ms
  └ rebuild_bi_zs    8.7 → 2.8ms
  └ add_indicators   4.4 → 3.5ms
  TF_DF lean        49.8 → 32.9ms
  TF_DF full        72.7 → 64.9ms

对拍用 git worktree 检出改动前的提交,同一份 BTC 1m 4000 根跑 38 项指纹:
full 模式 19 项全部一致(web 那条路没动);lean 模式差 2 项,正是设计要它差
的 klc.trend 和 klu.pattern,而 lean 下 bi/zs/seg/bsp/dataframe 全部一致——
这就是「这两个字段没人读」的实测证据:打空它们,下游一位不变。

瓶颈已经换位置了。新增 probe_inner.py 拆 inner_ms 分档:本机 TF_DF 两条腿占
70%、build_htf_zones 13%、htf_fx_timeline 6%,而服务端报的是 chan 构建 22ms /
信号链 86ms,机器差解释不了这个四倍差距。曾怀疑是 payload 反序列化,实测
_rebuild 只有 1.0ms,假设不成立。两边跑同一探针对分档表才能定位。

HANDOFF 顺带修掉一处 5.6 重号(增量落地那节改为 5.7,本节挂 5.71)。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 16:01:26 +08:00

124 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""把 `inner_ms` 拆成和本地一致的分档,用来定位两边测不一致的那部分。
起因:本地量到的构成是 TF_DF 占 ~80%、信号链 ~20%,服务器报的是 chan 构建
22ms、信号链 86ms。按机器差(×1.36)也解释不了四倍差距,说明两边测的不是
同一件事,或者有个环节只在服务器上贵。
用同一批窗口跑,比较分档而不是总数。两边都跑一遍再对表:
python research/live/probe_inner.py --syms BTC,ETH,SOL --repeat 5
分档口径(与 shadow_signal.compute 的调用顺序一致):
rebuild payload → DataFrame
ind_ltf/htf 两条腿各自的 add_indicatorsTF_DF 内部会做,这里单独计时)
chan_ltf/htf TF_DF 构建(lean
zones build_htf_zones
ladder add_zone_ladder
bsp find_fast_bsp3
timeline htf_fx_timeline5m 分型时间线)
attach attach_htf_agree + attach_zone_ladder
注意 `ind_*` 与 `chan_*` 在真实路径里是合一的(TF_DF 内部调 add_indicators),
这里拆开只为定位。总和会略大于实际 inner_ms。
"""
from __future__ import annotations
import argparse
import os
import sys
import time
import warnings
from pathlib import Path
import numpy as np
import pandas as pd
warnings.filterwarnings("ignore")
for _v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"):
os.environ.setdefault(_v, "1")
HERE = Path(__file__).resolve()
sys.path.insert(0, str(HERE.parents[1]))
sys.path.insert(0, str(HERE.parents[2]))
sys.path.insert(0, str(HERE.parent))
LTF_BARS, HTF_BARS = 2001, 801
def med(fn, n: int):
ts = []
out = None
for _ in range(n):
t = time.perf_counter()
out = fn()
ts.append((time.perf_counter() - t) * 1000)
return float(np.median(ts)), out
def probe(sym: str, repeat: int) -> dict:
from chanlun import TF_DF
from chanlun.analysis.fast_bsp import (
add_zone_ladder, attach_htf_agree, attach_zone_ladder,
build_htf_zones, find_fast_bsp3, htf_fx_timeline,
)
from lib.data import fetch_ohlcv
dl = fetch_ohlcv(f"{sym}/USDT:USDT", "1m", LTF_BARS * 3).tail(LTF_BARS).reset_index(drop=True)
dh = fetch_ohlcv(f"{sym}/USDT:USDT", "5m", HTF_BARS * 3).tail(HTF_BARS).reset_index(drop=True)
probe_tf = TF_DF(lean=True)
r = {"sym": sym}
r["ind_ltf"], _ = med(lambda: probe_tf.add_indicators(dl.copy()), repeat)
r["ind_htf"], _ = med(lambda: probe_tf.add_indicators(dh.copy()), repeat)
r["chan_ltf"], cl = med(lambda: TF_DF(dl, 1, "1m", lean=True), repeat)
r["chan_htf"], ch = med(lambda: TF_DF(dh, 1, "5m", lean=True), repeat)
cdf = cl.dataframe
r["zones"], z = med(lambda: build_htf_zones(cdf, "1m", chan=cl), repeat)
if z is None or z.empty:
r["n_zones"] = 0
return r
z0 = z.reset_index(drop=True)
r["ladder"], zl = med(lambda: add_zone_ladder(z0), repeat)
r["bsp"], sg = med(lambda: find_fast_bsp3(cdf, zl), repeat)
r["timeline"], tl = med(lambda: htf_fx_timeline(ch, ch.dataframe), repeat)
r["attach"], _ = med(
lambda: attach_zone_ladder(attach_htf_agree(sg, cdf, tl), zl), repeat)
r["n_zones"], r["n_sig"] = len(z0), len(sg)
# 增量口径:init 一次后追加,看稳态单根成本
c = TF_DF(lean=True)
c.init_stream(dl.iloc[:-60].reset_index(drop=True), 1, "1m")
ts = []
for k in range(len(dl) - 60, len(dl)):
t = time.perf_counter()
c.append_bar(dl.iloc[k])
ts.append((time.perf_counter() - t) * 1000)
r["append_bar"] = float(np.median(ts[20:]))
return r
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--syms", default="BTC,ETH,SOL")
ap.add_argument("--repeat", type=int, default=5)
args = ap.parse_args()
rows = [probe(s.strip(), args.repeat) for s in args.syms.split(",") if s.strip()]
d = pd.DataFrame(rows).set_index("sym")
parts = [c for c in ("ind_ltf", "ind_htf", "chan_ltf", "chan_htf", "zones",
"ladder", "bsp", "timeline", "attach") if c in d]
d["合计"] = d[parts].sum(axis=1)
pd.set_option("display.width", 220)
print("\n分档耗时(ms,中位)")
print(d[parts + ["合计", "append_bar"]].round(2).to_string())
print("\n占比(%")
print((d[parts].div(d["合计"], axis=0) * 100).round(1).to_string())
print("\n规模")
print(d[[c for c in ("n_zones", "n_sig") if c in d]].to_string())
print("\n注:ind_* 与 chan_* 在真实路径里合一(TF_DF 内部调 add_indicators),"
"拆开只为定位,合计会略大于实际 inner_ms。")
if __name__ == "__main__":
main()