花拾录
← 返回知识库

回测脚本被 O(N²) 拖到超时(退出码 124)——滑动指标该预计算缓存

编程语言导入2026/09/220 阅读0 评论

脚本逻辑明明是对的,小样本跑得也好好的,可数据量一上来就被系统杀掉,退出码 124。你翻遍日志找不到任何报错——因为根本没报错,是"跑得太慢被掐死了"。

现象

回测脚本或面板在跑全量数据时被强制终止:

$ python backtest.py
...(长时间无输出)
$ echo $?
124

退出码 124 是 timeout 命令的信号,含义是"进程超时被杀死"。进一步定位会发现,最慢的不是数据加载,而是某个"取状态"的函数——比如计算某个时间点上所有对象的指标。样本一大,它慢得离谱,CPU 满载却迟迟出不来结果。

根因

典型写法是"每个时间点都从头重算一遍":

def state_at(objs, t):
    result = []
    for o in objs:
        result.append(compute_ma(o, t))   # 对第 t 天,重算 MA
    return result

问题在于这个函数会被每个时间点各调用一次。假设有 M 个对象、N 个时间点,每个对象的指标数组长度是 N。现在的情况是:

  • 外层:对 N 个时间点各调用一次;
  • 内层:每次对 M 个对象各算一次;
  • 而"算一次"(compute_ma)本身又要遍历一段历史数据,代价随 N 增长。

三层叠起来,总操作量大致是 N × M × N 量级,也就是接近 O(N²)(在 M 固定时)。当 N 是"上百天"、M 是"上千个对象"时,就是上亿次重复计算——同一个对象、同一段历史,被反反复复算了无数遍。

核心浪费在于:同一个指标序列,本来只依赖对象自身和它过去的数据,与"你问哪个时间点"无关。 你在 t=1 时算过一遍它的完整序列,到了 t=2 又问一遍,等于把刚算过的东西删了重来。

解决

思路很简单:先一次性把每个对象的完整指标序列算出来并缓存,之后按索引取值,不再逐点重算。

# 预计算:每个对象一条完整指标序列,只算一次
series_cache = {o.code: compute_series(o) for o in objs}   # compute_series 返回长度 N 的数组

def state_at(t):
    return [series_cache[o.code][t] for o in objs]          # 直接按索引取,O(M)

这样总代价从"每个时间点重算一遍"的 O(N²·M) 量级,降到"一次性算完 + 每次 O(M) 取数"的 O(N·M) 量级。N 从上百天缩到只算一次,提速是数量级的。

如果指标是滚动窗口(比如 20 日均线),compute_series 本身也应该用增量更新的方式算——利用前一天的结果加一个新值、减一个旧值,而不是每次窗口都重新求和:

def compute_series(o, window=20):
    vals = o.close
    s = sum(vals[:window])
    out = [None] * window + [s / window]
    for i in range(window, len(vals)):
        s += vals[i] - vals[i - window]        # 增量:加新减旧
        out.append(s / window)
    return out

延伸与预防

一句话总结这条坑:滑动指标要么增量更新,要么预计算缓存——别每个点都从头来。

判断自己的脚本有没有这个问题,可以问三个问题:

  1. 有没有某个函数,在循环里被反复调用、每次传入不同的"时间/位置"参数?
  2. 它返回的结果,是不是只依赖"对象 + 位置",跟你在哪一次调用无关?
  3. 那么,能不能把"整条序列"一次算出来、后面只做数组下标访问?

三条都命中,基本就该预计算了。

预防层面:

  • 写完先做复杂度估算,哪怕只是粗略数一下"外层几次、内层几次、每次多重",O(N) 和 O(N²) 在数据翻倍时表现天差地别。
  • 小样本量测速,拿 10% 的数据跑一遍计时,按 N² 外推就能预判全量会不会超时。
  • 给脚本加超时与进度输出,被 124 杀掉时至少知道卡在哪一步;有条件的话用 cProfile 直接找出热点函数,别靠猜。
  • 如果确实存在"必须按位置查询"的场景,记忆化(memoization) 也是同一条思路的轻量版:用一个字典缓存"对象 + 位置 → 结果",命中直接返回,效果往往立竿见影。

评论(0)

  • 还没有评论,来抢沙发~

相关文章