forkd基准测试方法论:如何在你的机器上复现100个microVM、每沙箱仅0.12MiB内存的极限数据
【免费下载链接】forkd高性能Agent沙箱,预热虚拟机可以在约 100 毫秒内派生出 100 个独立实例;运行过程中约 150 毫秒“分叉”出一个新的运行环境。底层使用 KVM 隔离,并利用快照+写时复制降低资源开销。项目地址: https://gitcode.com/deeplethe/forkd
这篇文章完整拆解forkd 基准测试的方法论:在同一台 Linux 主机上,用一套开源测试脚本,复现「101 ms 派生 100 个 microVM」与「每个沙箱仅占 0.12 MiB 主机内存」这两个核心数字,并与 Docker、gVisor、Firecracker 冷启动做同场对比。所有测试脚本都在仓库 bench/ 目录下开源,克隆后即可运行。
30 秒版本(TL;DR)
- 测试机:裸金属 Ubuntu 24.04 / Linux 6.14 / 20 vCPU / 30 GiB / KVM(i7-12700,无嵌套虚拟化)
- 测试负载:100 个沙箱并行,每个执行
numpy.zeros(5).tolist()并返回结果- 计时口径:从第一个沙箱请求发出,到最后一个沙箱确认返回结果,计墙钟时间(wall-clock)
- 内存口径:沙箱全部起来并稳定后,用
free -m的主机物理内存前后差值 ÷ 100
一、测试负载定义:所有后端跑同一道题 🧪
公平对比的前提是题目完全一致。forkd 的基准定义非常克制,就一件事:
| 要素 | 定义 |
|---|---|
| 并发数 | 100 个沙箱并行拉起 |
| 验收条件 | 每个沙箱都能执行numpy.zeros(5).tolist()并返回正确结果 |
| 计时起点 | 第一个沙箱的创建请求发出 |
| 计时终点 | 最后一个沙箱确认执行结果 |
| 测试机 | Ubuntu 24.04 / Linux 6.14 / 20 vCPU / 30 GiB / KVM |
其中有一个关键的「公平性预算」设计:forkd 的父快照在暂停前已经 import 过 numpy(预热到 PID 1),所以 100 个子沙箱恢复后直接复用内存里现成的运行时;而 Docker、gVisor 每次都要在容器里冷启动并重新 import numpy。这不是给谁放水——这正是 forkd「从暖快照分叉」的设计本意,其他后端则按各自最合理的用法测量。
完整的负载定义见 bench/README.md,端到端测试脚本是 bench/bench-spawn-100.sh,多后端统一驱动器是 bench/compare-all.py。
二、测试机自检:先确认你没有在「套娃」里测 📋
基准数字对硬件极其敏感,forkd 团队在 bench/CUBESANDBOX.md 里明确要求:所有对比项目必须在同一台裸金属主机上测量,不允许嵌套虚拟化。两条命令即可自检:
systemd-detect-virt # 应输出 none(裸金属) grep -o vmx /proc/cpuinfo | head -1 # 应输出 vmx(VT-x 可用)安装完成后跑forkd doctor,它会执行 17 项检查(KVM、cgroup v2、tap 设备、Firecracker 版本、快照目录磁盘空间等),每项不通过都会给出修复提示——这也是正式开测前最值得花的一分钟。主机一键准备脚本:
sudo bash scripts/setup-host.sh # KVM + tap 设备 sudo bash scripts/host-tap.sh # 一次性 tap 配置 sudo bash scripts/netns-setup.sh 100 # 100 个子沙箱各自的网络命名空间三、三步复现:从克隆仓库到重绘图表 📥
第 1 步:克隆仓库并安装
git clone https://gitcode.com/deeplethe/forkd第 2 步:构建「标准父快照」pyagent
基准图里测量的正是 recipes/python-numpy/ 这个最轻量的 Python 父镜像——python:3.12-slim+ numpy,rootfs 约 1.5 GB,预热后内存镜像约 512 MiB。构建 rootfs、创建 tap、准备 netns 后执行一次forkd snapshot --tag pyagent ...即可(完整命令见 recipes/python-numpy/README.md)。
第 3 步:跑测试脚本、重绘图表
# 跑全套基准(forkd / Docker / gVisor / Firecracker 冷启动),输出 JSON sudo -E bash bench/bench-spawn-100.sh > /tmp/results.json # 用你的实测数据重绘两张 PNG 图表 BENCH_RESULTS=/tmp/results.json python3 bench/generate_charts.pybench/generate_charts.py 里内置了一份官方基线数据(DATA字典),用环境变量BENCH_RESULTS指向你的 JSON 就能整体覆盖——也就是说,官方图表本身就是「脚本产物」而非手工画的,任何人可以原样重放。
只想快速摸底?跳过全套流程,对任意快照跑forkd bench --tag pyagent --n 5,它会在你的机器上一次性测出 spawn、exec 往返、branch、fan-out、cleanup 五段耗时,输出对截图非常友好。
四、0.12 MiB 是怎么测出来的?🧮
内存数字的测量逻辑在 bench/compare-vs-docker.sh 中,思路简单粗暴但严谨:
- 拉起沙箱前,
free -m读取主机已用物理内存作为mem_before(先 sleep 1s 让内核页缓存噪声沉降); forkd fork --tag ... -n 100拉起 100 个子沙箱,再 sleep 1s 等待稳定;- 重新读取
mem_after,差值 ÷ 100 = 每沙箱内存增量。
为什么 forkd 能做到 0.12 MiB/沙箱?因为每个子 VM 都用mmap MAP_PRIVATE映射同一个父内存镜像文件,页级写时复制(copy-on-write)由 Linux 内核完成:只要子沙箱不去改写某页,它就和父 VM 共用物理内存。跑完那条 numpy 表达式后几乎没有脏页,所以 100 个沙箱只比 1 个父 VM 多占约 12 MiB 总量。作为对照,同样 512 MiB 配置的 Firecracker 冷启动每个 VM 实测吃 84 MiB 主机内存。
五、如何正确解读这两张图表 📊
派生耗时图上 forkd(101 ms)与其余后端差距巨大,但有三点必须说清楚,否则容易「被图表误导」:
- 量级可复现,毫秒数不必相同。官方 Notes 明确写着:不同 CPU、内核、KSM 调优下数值会漂移,能复现的是数量级,不是精确毫秒。你的机器上 forkd 可能是 80 ms 也可能是 150 ms,这都算「复现成功」。
- 这是「暖快照分叉」对「冷启动」的跨工况对比。forkd 的 101 ms 来自恢复预热快照;其他后端是全新冷启动。两者是不同的工作点,不是同一种原语——forkd 团队在 docs/COMPARISON.md 中对此有专门说明。
- 注意区分快慢路径。比如 CubeSandbox 一行 1056 ms 是「池化快路径」,慢路径实测 20 s 且只有 77% 成功率,方法论文档 bench/CUBESANDBOX.md 把两条路径和原因都如实记录在案。
六、诚实的注意事项 ⚠️
- Docker / gVisor 的数字包含每个沙箱的冷
import numpy开销,这是给它们的公平预算,forkd 因父 VM 预热而免除此开销; - 不要在云主机嵌套虚拟化环境里测 KVM 项目,数字会劣化一个量级且不可比;
- 0.12 MiB 是空闲分叉后的增量,如果你的负载让每个子沙箱写很多脏页(比如各跑一份 ML 推理),每沙箱内存会随分叉量上升,请用你自己的父快照实测;
- 快照不可跨 Firecracker 版本、不可跨 CPU 微架构移植,
forkd doctor会帮你校验版本一致性。
总结:forkd 基准测试的全部秘密就是「同一台裸金属主机 + 同一道 numpy 小题 + wall-clock 计时 + free -m 差值内存」四件套。脚本在 bench/,图表数据在 bench/generate_charts.py,方法论笔记在 bench/README.md——克隆仓库,跑一遍bench-spawn-100.sh,你就能在公告栏贴出属于你自己机器的那两张图。
【免费下载链接】forkd高性能Agent沙箱,预热虚拟机可以在约 100 毫秒内派生出 100 个独立实例;运行过程中约 150 毫秒“分叉”出一个新的运行环境。底层使用 KVM 隔离,并利用快照+写时复制降低资源开销。项目地址: https://gitcode.com/deeplethe/forkd
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考