服务端把增量上线后回传两个热点: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>
124 lines
4.6 KiB
Python
124 lines
4.6 KiB
Python
"""把 `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_indicators(TF_DF 内部会做,这里单独计时)
|
||
chan_ltf/htf TF_DF 构建(lean)
|
||
zones build_htf_zones
|
||
ladder add_zone_ladder
|
||
bsp find_fast_bsp3
|
||
timeline htf_fx_timeline(5m 分型时间线)
|
||
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()
|