🏠 首页性能评测内存管理和类型 RFC符号执行路线图C 互操作
EN 中文

内存管理:Silu 的两套纪律(2026-10-25, v1.9.0 实测)

Silu 没有 GC。静态世界(typed 路径)用 Rust 式所有权规则,
动态世界(pyobj)故意泄漏。本文给出机制、实测数据与
"为什么静态世界不需要借用检查器"的分析。

一、静态世界:所有权规则(无 GC,无借用检查器)

类型化路径不产生需要追踪的垃圾——每块内存从出生就有唯一主人:

类型 表示 生命周期管理
int / float / bool栈值(i64/double/i1)无堆分配
str(rawstr)独占堆缓冲 {len,cap,data,cp}完整 drop 体系:赋值拷贝时旧缓冲 free;函数退出统一 drop 全部局部槽(_emit_local_rawstr_drops);传参借用零拷贝(被调方只读,s += x 重绑走 concat 新建——Python 别名语义精确成立:h(d) 返回 "abx" 而 d 保持 "ab")
list[int] 等 rawlist独占结构体赋值即所有权转移;无作用域 drop(静态程序容器通常一次构建)
dict[int,int] 等 rawdict独占哈希表同上

实测(Apple Silicon):

二、动态世界:故意泄漏(leak by design)

pyobj 盒({i32 tag, i8* data};create_int/str/list/dict/instance) malloc 后永不 free——运行时不存在用户盒的释放路径。

实测:30 万次循环、每次建一个小 list + dict,RSS 172MB (~570B/迭代,纯泄漏)。

这是有意的取舍:

0.9× Rust 的热路径上全部买单;

(程序结束,操作系统统一回收;类 arena 分配器用法)。

三、解释器平面

silu interpret / silu exec / pycompat 跑在 CPython 上: Python 的引用计数 + 分代 GC,与语言语义一致(四平面验证因此 能抓住编译面的内存 bug——例如 v1.7.1 写回回归就是解释器平面 先暴露的)。

四、为什么静态世界不需要借用检查器?

Rust 借用检查器解决的问题是:**同一块内存可能有多个名字且 都可能写**。Rust 的答案是编译期证明"要么唯一写者,要么多个 只读者"(borrow checker)。

Silu 静态世界不需要它,因为语言语义更保守——三个根本差异:

  1. 不可变值语义。str 逻辑上不可变(Python 语义):s += x

是重绑(新串),不是原地写。原地 append 只是编译器在 槽独占前提下的优化发射;一旦槽可能是借来的(str 参数), 发射退回 concat 新建(_borrowed_strs 集合跟踪)。"写"这个 动作永远不发生在共享内存上。

  1. 共享即只读。str 传参借用 = 被调方拿到指针但类型系统保证

它只能读(s[i]、len、切片都产新值)。这是借用检查器的 核心规则,但 Silu 不需要检查器来强制——**语言里根本没有 通过共享引用写的语法**(无 &mut,无原地变异方法)。Rust 需要检查器是因为它*提供*了 &mut T;Silu 不提供。

  1. 容器的引用语义对齐 Python。list/dict 赋值是别名

(Python 语义),共享可变——但这正是 Python/Rust-Rc<RefCell> 式的运行时对象,靠独占 malloc + 程序员心智模型管理,静态 程序里它们通常一次构建终身使用(基准 8/14 静态程序里容器 装载后从不重新绑类型)。

一句话:**Rust 要检查借用,因为它把"可变别名"作为语言能力 交给你;Silu 静态子集不给你这个能力,所以无从检查、也无需 检查。**代价是表达力上限(写不了 Rust 那种零拷贝原地算法的 通用形式——原地 append 只对槽独占这个窄窗口成立);换来的是 编译器无需借用检查器即可保证内存安全。

附:Rust vs Silu 静态世界速览

维度 Rust Silu 静态世界
内存安全由谁保证借用检查器(编译期数据流分析)语言语义(值不可变 + 无共享写语法)
可变别名提供(&mut T,需检查)不提供(无对应语法)
借用规则NLL 检查器推导(有时与意图不符,需改结构)无需推导——规则内建于发射策略
原地变异通用(&mut vec 直接 push)窄窗口优化(仅槽独占可证时发射;借来的槽自动退回 concat)
共享引用&T(只读,检查器强制)str 借用传参(只读,语法上只读——没有写它的方法)
容器别名受限(Rc<RefCell> 逃生舱)Python 语义(自由别名,运行时对象)
生命周期标注'a 泛型参数不存在——值语义让生命周期 = 词法作用域
表达力上限高(零拷贝原地算法的通用形式)低(原地优化是编译器私事,程序员不可申请)
编译器复杂度借用检查器本身是研究级组件发射期 3 个 set 跟踪(owned/borrowed/scope-local)

一句话对比:Rust 把内存安全做成编译器检查的权限系统, Silu 把它做成语言不给危险语法的事实——前者换来表达力, 后者换来编译器简单(而这个编译器本身是 Silu 写的 1.2 万行、 能逐字节重建自己的代码,简单性会复利)。

五、将来要治吗(按性价比)

  1. sum-type 路线二:收窄动态世界,泄漏表面积随 pyobj 用量

一起缩(3-4 周级,也是 jsonparse/wordcount 性能的根治)。

  1. rawlist/rawdict 作用域 drop:照抄 rawstr 的

_emit_local_rawstr_drops 配方(1-2 天)。

  1. pyobj 引用计数:盒布局加计数头、全部装箱/传递插桩——

代价最大,仅长期驻留服务需要;当前无动机。

三项均不在 todo(无性能/正确性压力)。


附:复现命令——

python -m silu build /tmp/gc1.si -o gc1 && /usr/bin/time -l ./gc1   # 标量 1MB
python -m silu build /tmp/gc2.si -o gc2 && /usr/bin/time -l ./gc2   # str 27MB
python -m silu build /tmp/gc3.si -o gc3 && /usr/bin/time -l ./gc3   # pyobj 172MB(泄漏)