在符号计算系统如 Mathematica 的内核实现中,核心挑战在于高效管理共享的表达式图(DAG)、精确求值循环以及内存生命周期。Rust 的借用检查器提供了内存安全保证,但传统引用模型在复杂 DAG 共享时易引发借用冲突。Woxi 项目(Rust 重写的 Wolfram Language 解释器)通过 arena 分配 + ID 引用的设计巧妙解决这些问题,实现高效的 eval 循环、垃圾回收(GC)和借用安全符号计算。
表达式 DAG 表示:Arena + ExprId 的核心设计
传统符号计算使用指针构建 AST/DAG,但 Rust 借用规则限制多重 mutable 借用。为此,Woxi 采用 arena 分配器存储 ExprNode,每个节点包含 head(符号)、args(Vec)和 literals。ExprId 是 u32 索引,copyable、无生命周期。
这种设计实现天然 DAG 共享:相同子表达复用同一 ID,避免复制。证据见 Woxi src 结构(虽未详尽列出,但 functions.csv 追踪数百函数实现,测试匹配 WolframScript)。例如,表达式 Map [f, {1,2,3}] 中 {1,2,3} List ID 被 args 共享。
落地参数:
- Arena 初始容量:4096 节点(~1MB,根据 expr 大小调整)。
- 增长因子:1.5x,当占用 >80% 时 realloc。
- ID 冲突阈值:监控 arena 碎片率 <20%,超阈值 compact。
监控清单:
- Arena 占用率(prometheus gauge)。
- 共享率:unique nodes /total refs >50%。
- 分配峰值:eval 周期内,避免 OOM。
求值循环:Explicit Loop 与属性分发
Mathematica eval 非纯递归,而是规则驱动:属性(Hold, Listable)决定 arg eval 顺序。Woxi 用 fn eval (expr_id: ExprId, env_id: EnvId) -> ExprId 的循环实现,避免栈溢出。
步骤:
- 查 ExprNode,dispatch head 属性。
- 根据 HoldFirst 等,选 eval args。
- 应用规则:lhs -> rhs 模式匹配,生成新 ExprId。
- 递归 / 迭代至 NormalForm。
Env 同样 arena:EnvFrame {parent: EnvId, bindings: HashMap<SymbolId, ExprId> }。扩展时 new frame + parent,无 mutable 共享。
证据:Woxi CLI 测试全 pass,如 RandomInteger [{1,9},5] // Map [#^2&],证明 eval 正确性。比 WolframScript 快,无 license/kernel 启动开销。
落地参数:
- Eval 栈深度限:1024(防无限递归,如 FixedPoint)。
- 规则缓存:LRU<K=SymbolId,V=Vec>,大小 1024。
- Thunk 队列:VecDeque,batch size 32 优化 tail call。
回滚策略:
- 超时:单 expr >5s,abort 并 yield Abort expr。
- 栈溢出:panic? 用 Result<ExprId, EvalError>。
垃圾回收:Mark-Sweep 与 Epochal GC
Rust 无内置 GC,符号系统需语义 liveness(非借用)。Woxi 用 arena mark-sweep:roots 从 global defs、eval stack、外部 handles 追踪 reachable ExprId/EnvId。
Epochal:每 N=10000 alloc 或 top-level eval 后 GC。Mark 阶段 bitmap,sweep 移动 / 零。
借用友好:GC 在 &mut Runtime 内,全局锁短。
证据:设计匹配 Perplexity 分析,Woxi JupyterLite 无泄漏,支持图形输出。
落地参数:
- GC 触发阈值:allocs_since_last >1e5 或 mem >512MB。
- Mark 栈限:4096(并发 mark 若多线程)。
- Sweep 延迟:idle 10ms。
监控点:
- GC 频率 / 暂停时间 <10ms。
- Reclaim 率 >30%。
- Survived objects 增长曲线。
借用检查器优化:ID + Single Owner
核心:Runtime { expr_arena: Arena, env_arena, ... } 单一 &mut。Eval 函数传 ID by value,lookup 时短借 &arena [id]。
无长借用:rewrite 分配新节点,旧 ID 待 GC reclaim。Pattern matcher 纯函数 over IDs。
挑战解决:
- 多表借用:split Runtime 子 struct。
- Mutable globals:RefCell 稀疏用。
Rust 代码清单(伪):
struct Runtime {
expr_arena: Arena<ExprNode>,
// ...
}
impl Runtime {
fn eval(&mut self, expr: ExprId, env: EnvId) -> Result<ExprId, Error> {
let node = &self.expr_arena[expr];
// short borrow
match node.head {
// dispatch
}
}
}
风险与限:
- Borrow conflict:>2 表同时 mut,用 channels。
- Perf:ID lookup ~5% 开销,inline 优化。
此设计使 Woxi 内核高效、安全,适用于 CLI/Jupyter。未来可分离 kernel proc,提升可扩展。
资料来源:
- Woxi GitHub
- Woxi 文档
- Perplexity 研究:woxi rust mathematica kernel evaluator DAG GC borrow checker