在 actor-based 并行编程语言中,实现高效的内存管理是关键挑战。Plush 语言采用 per-actor 的复制式垃圾回收(Copying GC),基于 Cheney 算法,利用半空间(semispace)设计,每个 actor 拥有独立的私有堆和邮箱堆。这种设计避免了全局锁,提高了分配速度和并发性能,同时通过活对象迁移和内存压实,确保堆内存紧凑利用。
复制式 GC 的核心在于将堆分为 fromspace 和 tospace 两个等大空间。垃圾回收触发时,从 fromspace 中的根对象开始扫描,复制所有可达活对象到 tospace,并使用转发指针(forwarding pointer)记录新位置。扫描过程采用深度优先或广度优先遍历根集,包括 actor 的调用栈帧、全局变量(子 actor 启动时复制)和邮箱消息队列。这种根扫描确保不会遗漏引用,同时复制过程天然完成内存压实:活对象被顺序放置在 tospace 开头,形成连续块,避免碎片化。
在 Plush VM 中,分配采用 bump pointer:私有堆使用快速的无锁 bump 分配器,直接从当前堆顶推进指针。新对象只需检查堆剩余空间,速度极快。邮箱堆用于接收消息,发送方锁定接收者邮箱分配器进行复制,确保接收 actor 无需中断执行。证据显示,这种双分配器设计使 ping-pong 消息传递基准在 M1 MacBook 上 500k 次仅需 2 秒,证明了高效性。GC 暂停时间短,例如 Amiga Boing Ball 演示在 60fps 下每秒 GC 一次,用户几乎无感知。
落地实现时,需要关注以下参数和清单:
-
堆大小与增长策略:
- 初始私有堆:1MB,邮箱堆:512KB。
- 增长阈值:占用率达 75% 时翻倍(上限 256MB,避免 OOM)。
- 公式:new_size = min (current_size * 2, max_heap),使用 Rust 的 Vec 实现 semispace。
-
GC 触发阈值:
- 私有堆:fromspace 占用 > 50% 时触发(平衡暂停与吞吐)。
- 邮箱堆:队列长度 > 1000 消息或占用 > 80% 时,发送方 backpressure(重试机制)。
- 监控:记录 GC 频率(目标 < 1s / 次)、暂停时间(< 10ms)。
-
根扫描与对象布局:
- 根集:栈(每个帧的局部变量引用)、globals map、mailbox queue。
- 对象头:8 字节(标记 + forwarding ptr),支持 64-bit 引用。
- 复制函数伪码:
fn copy(obj: *mut Object) -> *mut Object { if forwarded(obj) { return forward_ptr(obj); } let new = bump_alloc(size(obj)); copy_fields(obj, new); set_forward(obj, new); return new; } - 压实收益:活对象率 25% 时,回收 75% 空间。
-
优化与监控点:
- 避免大消息:>1KB 对象分拆或共享不可变数据(未来扩展)。
- 性能瓶颈:大链表 GC 慢(>300K 对象),检查 hashmap 或 quadratic scan,使用 profilers 如 perf。
- 回滚:若 GC 失败,fallback 到保守标记(但 Plush 用精确 GC)。
- 指标:分配速率(>1GB/s/actor)、GC CPU 利用(<20%)。
这种机制特别适合 actor 系统:独立 GC 无需 stop-the-world,复制开销被现代 CPU memcpy 速度抵消(10GB/s+)。相比标记 - 清扫,复制式提供零碎片和快速分配,代价是双倍堆空间(可通过 generational 优化)。Plush 证明,即使玩具语言,也能实现生产级内存管理,支持实时 3D 图形。
资料来源: