架构决策:求值引擎¶
背景¶
最初的实现用「递归闭包模式」来组合谓词逻辑。这种写法语法优雅、实现简单,但在规模化的profiling下,暴露出 Python 执行模型里几处结构性的缺陷。
本文记录迁移到迭代式 AST 引擎的理由。
Profiling:闭包陷阱¶
对深规则链(depth > 100)做 profiling,暴露了闭包方案的三个问题。
1. 栈帧开销¶
Python 解释器为每次函数调用都付出可观的开销(PUSH_FRAME / POP_FRAME)。
CPU profiling 显示,执行时间被栈管理主导,而非真正的逻辑求值。深度 2000 的逻辑链会建出深度 2000 的调用栈,立刻撞上 sys.getrecursionlimit() 的墙,并因 CPU 缓存未命中而非线性地劣化性能。
绝大部分时间都耗在等待闭包返回上。
下面是一座摩天楼
谓词嵌套超出栈深度限制时也会触发 RecursionError,但这在生产环境中极不可能发生。
2. 内存碎片与 GC 不稳定¶
闭包是不透明的对象,持有对其执行上下文(cell)的引用。
基准测试显示,高深度下执行时间的标准差(StdDev)极大。
构建深链会因大量临时可调用对象而触发激进的 GC 周期,导致不可预测的延迟尖峰(P95 离群值)。
3. 零自省能力(「黑盒」)¶
编译后的闭包对运行时是不透明的。
一个组合好的谓词只是 <function <lambda> at 0x...>。当一条规则求值为 False 时,如果不借助侵入式的日志,既无法追踪是哪个节点失败,也无法检视中间状态。
方案:AST 模式¶
我们用一个显式的迭代式 AST 引擎替换了递归执行模型。
设计原则¶
- 规则定义为数据结构(节点),而非编译后的函数。
- 用单个循环处理整棵树。
- 规则结构是不可变的,执行状态是临时的。
收益¶
| 特性 | 闭包(旧) | AST 引擎(现) |
|---|---|---|
| 控制流 | 递归调用栈 | while 循环 / 栈机 |
| 空间复杂度 | 栈帧 | 栈帧(仅堆上) |
| 稳定性 | 高抖动(GC 颠簸) | 确定 / 线性 |
| 可调试性 | 无(不透明) | 完全可自省(可访问节点) |
结论¶
转向基于 AST 的引擎,让我们能以线性的性能特征和常数的栈占用支撑任意复杂的规则链,同时解锁了闭包模型下不可能实现的序列化、单步调试等能力。

