面试准备以 PLM 为主。这里用一个短的全量复制实现交代“干净基线 → 私有 Guest”,不做 Linux COW,不做性能测量,也不把真实共享页留作必须完成的任务。第一次学习可以先跳过本章。
运行
go run ./cmd/demo -prepared-copy
# {"item": "book", "total": 42}
go run ./cmd/demo -source examples/order.py -prepared-copy -plm
# {"item": "book", "total": 47}
go run ./cmd/demo -source examples/order.py -prepared-copy -prefix
# {"item": "book", "total": 47}
-prepared-copy 只选择 Guest 的创建方式;PLM、prefix、工具注册、结果交付、Future 和取消仍是原有路径。默认不加这个参数时仍采用 fresh 初始化。
只读 prepared.go
prepared.go 只有两个核心函数:
NewPrepared:先像New一样建立 runtime/编译代码;创建一个参考 Guest,运行_initialize和 CPythoninit;把此时的整份线性内存复制到 Runner 私有的image,关闭参考 Guest。不运行用户代码,也不开放自定义 warmup 脚本。newGuest:为每轮创建独立 Wasm 实例和 I/O,调用_initialize。fresh 模式继续调用 CPythoninit;prepared 模式将内存增长到基线长度,然后用Memory.Write(0, image)全量复制,跳过再次调用 CPythoninit。
NewPrepared: 参考 Guest → init 完成 → 复制为 Go image → 关闭参考 Guest
每轮运行: 新 Guest → _initialize → 全量复制 image → 普通 / PLM / prefix
为什么在 _initialize 之后复制?实例化的 data segments 和运行库初始化会写内存。先复制再初始化,可能覆盖已准备的状态。
Runner.image 从参考内存复制得到,不借用已关闭 Guest 的切片;实例的 Memory.Write 又复制一份,所以实例之间和基线之间没有可写别名。Runner.Close 释放对 image 的引用。关闭必须发生在本 Runner 的所有 Run 返回之后,与原有约定相同。
full-copy 与 COW 的区别
- 当前 full-copy:每个实例付出整份内存复制和私有存储,借此说明基线恢复及写入隔离。
- 真正 COW:可共享尚未修改的物理页,写入时才分离。当前没有
memfd、MAP_PRIVATE或页共享。 - 两者都可以让程序看到私有状态,但不能据此声称内存成本或启动性能相同。本项目没有测这些指标。
Go 的 runState、Host 工具、Future 和 prefix producer 不放进 image。基线是在任何用户源码、inputs、工具调用和 prefix 接收之前捕获的;复制一个已经带请求数据的运行中 Guest 不是这里支持的操作。
范围:不是通用 Wasm checkpoint
只支持本仓库固定 Guest 在干净 init 返回后的内存基线,不捕获任意执行栈、Wasm globals/tables 或 Host 文件描述符等外部状态。新实例自己的 _initialize 和静态初始化仍然执行。
当前 artifact 的这个静止初始化边界已通过真实执行验证。若以后增加会改变这些边界的 Guest 初始化、外部资源或可变非内存状态,要重新判断能否这样复制;不为未发生的需求预建通用 snapshot 框架。
本轮没有改 Guest 源码或打包方式,沿用阶段 3 的真实 Wasm;实现和验证在本机 macOS 完成,不需要 Linux 工具或本地 WSL。
测试在回答什么
prepared_test.go 检查基线真实存在、捕获没有调用 Host 工具,并有 7 个真实 Guest 子测试:
- 普通 Host 工具返回值正确。
- 一轮修改
builtins,下一轮看不到。 - 两个同时存活的实例:写一个实例的内存不改变另一个,也不改变 image。
- PLM 的独立工具仍能用启动信号握手证明重叠。
- prefix 的工具仍在源码 EOF 前启动。
- Python 错误不改变后续实例的基线。
- 中止无限循环后,下一轮仍正常。
这是功能和生命周期检查,不是复制速度/内存 benchmark。