
Peri 沙盒内存占用研究
编写于 2026-08-12
这是一次单机、单时点的观察性快照:50 个 Peri 沙盒容器和 7 个 opencode 进程的任务构成并不匹配。它能描述该时点的内存分布,不能代表一般资源成本,也不能独立证明语言、运行时或架构造成差异。字节按二进制前缀(1 MiB = 1,048,576 B)。
数据是怎么来的
数据来自生产环境的一次 docker stats 快照(2026-08-12):50 个沙盒容器、同一台主机、每个都是 debian-slim 基础镜像加一个 peri 进程、内存上限 512 MiB。快照时刻的内存从 49.21 MiB 到 338.8 MiB 不等(占限制的 9.6% ~ 66.2%)。
作为参照,同一天还用 top 实测了 7 个 opencode 进程——均值 414.0 ~ 483.0 MiB,同样包含操作系统(细节见下文”与 opencode 对比”)。
看数字前先说明口径(这几条会影响所有数字的解读):
- MEM USAGE 含 page cache。docker stats 报的是 cgroup 内存使用,容器读文件留下的页缓存也算在内,数值高于纯常驻内存(RSS)。
- NET / BLOCK I/O 是累计值(容器从创建到现在的总量),用来判断”这个容器跑过多少活”,不代表快照时刻的状态。
- PIDS 是快照时刻的进程数:空闲沙盒固定 10 个,活跃沙盒 16 ~ 57 个。
- 快照只有一次,没有时间序列。“基线""活跃”这些分层由 I/O 和 PIDS 推断,没有进容器做进程级观察。
大多数沙盒不到 105 MiB
| 指标 | 值(MiB) |
|---|---|
| 均值 | 92.29 |
| 中位数 | 74.09 |
| 最小值 | 49.21 |
| 最大值 | 338.8 |
| P10 / P25 / P75 / P90 | 67.5 / 68.7 / 101.0 / 104.7 |
按区间看:
| 区间 | 容器数 |
|---|---|
<60 MiB | 4 |
| 60 ~ 75 MiB | 22 |
| 75 ~ 105 MiB | 20 |
| 105 ~ 150 MiB | 2 |
>150 MiB | 2 |
口径:单次快照;每格一个容器,按内存升序;颜色对应五层;格内为该容器内存值;前 46 格低于 105 MiB,后 4 格为 110.2 / 116.9 / 280.2 / 338.8 MiB
先说最直观的:92% 的沙盒(46/50)低于 105 MiB。 22 个容器挤在 67.09 ~ 74.21 MiB 的 7 MiB 窄带内(占 44%),另有 20 个落在 91.08 ~ 104.7 MiB。中位数(74.09)与均值(92.29)的差距完全由尾部 4 个容器(110.2 / 116.9 / 280.2 / 338.8)拉出——均值被 2 个活跃容器拉高了约 18 MiB。
这台机器上 50 个隔离沙盒的快照总内存约 4.5 GiB。浏览器标签或空 Node 进程的通用经验值不是本次实验数据,不能用来建立等价比较;容器隔离的 CPU、存储、网络与运维成本也不在这张快照中。
另一个特征:分布两极化。 50 个容器要么是”基线态”(<105 MiB,46 个),要么是”活跃态”(>150 MiB,2 个),105 ~ 150 MiB 之间只有 2 个样本。没有”缓慢爬升”的中间形态,这与”每任务起一个沙盒、任务结束后沙盒保持待命”的部署模式一致(机制推演,下文解释)。
内存随负载分层
按内存分组后,各组的网络与块 I/O 呈现明显的阶梯模式:
| 层 | 容器数 | 内存(MiB) | 网络入(累计) | 块读(累计) | PIDS |
|---|---|---|---|---|---|
| 层 1 刚创建态 | 4 | 49.21 ~ 58.03 | <120 kB | 0 ~ 63 MB | 10 |
| 层 2 基线态 | 22 | 67.09 ~ 74.21 | 13.1 ~ 13.4 MB | 188 ~ 290 MB | 10 |
| 层 3 任务态 | 20 | 91.08 ~ 104.7 | 16.6 ~ 68.2 MB | 195 ~ 277 MB | 10 |
| 层 4a 写盘任务 | 2 | 110.2 / 116.9 | 26 / 27.4 MB | 3.6 MB / 344 kB | 22 / 16 |
| 层 4b 密集任务 | 2 | 280.2 / 338.8 | 813 MB / 423 MB | 1.14 GB / 154 MB | 20 / 57 |
口径:网络入为容器生命周期累计值;x 轴按网络入分档(与内存无关),档内圆点为各容器内存,圆点横向抖动仅避免重叠;黑点为档内均值,连线呈阶梯;25 MB 档内两个高点(110/117 MiB)为写盘任务容器
这张表最突出的特征:每个层内部几乎不波动。 层 2 的 22 个容器网络入流量全部落在 13.1 ~ 13.4 MB 的 300 kB 带宽内;层 3 内部又分两档——10 个容器内存 91.08 ~ 97.26 MiB,其中 9 个网络入 24.6 ~ 27.5 MB(1 个为 16.6 MB)、另 10 个网络入 60.4 ~ 68.2 MB、内存 100.9 ~ 104.7 MiB。档内方差极小,档间差距整齐。
这是”固定任务模板”的痕迹(推断)。 生产环境为同类任务起同类沙盒:网络入 13.2 MB 对应基线任务(下载固定依赖包),25 MB / 65 MB 对应两档更大的任务。内存随任务包大小阶梯上升,而不是连续分布。证据链:层 2 网络入 13.1 ~ 13.4 MB 高度恒定(22/22 一致);层 3 两档各 10 个容器档内一致。此结论为推断——单次快照无法区分”任务模板”与”巧合聚集”,反证见下文。
层 1 的 4 个容器网络入 <120 kB、块读 ≤63 MB,明显低于其余 46 个(块读均 >188 MB)。推断为刚创建、尚未执行任务流程的沙盒(这是推断:未见任务痕迹,无法直接确认创建时间)。
层 4 的差异在 PIDS 与块写:110.2 / 116.9 MiB 的两个容器 PIDS 为 16 / 22、块写 337 MB / 268 MB——进程数多、写盘量大但网络与层 3a 相同。338.8 MiB 的容器 PIDS = 57,是全表进程数最多的,推断为并行子进程场景(构建、测试类任务);280.2 MiB 的容器 PIDS 仅 20,但块读 1.14 GB、块写 2.61 GB,是全表磁盘流量最大的。
为什么这么轻
基线(层 2,67 ~ 74 MiB)的组成:debian-slim 运行态(含基础进程)+ peri 进程 + 10 个初始化进程的固定成本。4 个层 1 容器(49 ~ 58 MiB)说明”更小的态”存在——两者差距约 10 ~ 20 MiB,推测为任务执行后的残留(进程映像、页缓存、临时文件),但未做进程级验证。
可能影响基线的三个设计选择如下,但本次快照没有做消融实验,不能判定各自贡献:
- peri 本身是 Rust 编译的原生二进制。 这意味着 Peri 进程不要求语言解释器;但本次快照没有进入容器核对其他进程,不能断言镜像中没有 Node、Python 或任务启动的运行时。
- debian-slim 是刻意选择的极简基础镜像。 它保留了系统调用与包管理能力(任务要装依赖),但去掉了绝大多数桌面与文档内容。镜像越小,运行态越轻。
- 512 MiB 限制在这次快照中没有被触达。 峰值为 338.8 MiB(占限制 66%),90% 的容器在 105 MiB 以下;这不能证明其他任务、长时间运行或峰值瞬间也足够。
负载增量与什么相关(单次快照,50 样本):
- 内存 vs 网络入流量:相关系数 0.88
- 内存 vs PIDS:相关系数 0.84
两个强正相关,但这是相关性,不是因果证明。网络流量大的容器内存高,可能因为任务本身需要更多进程与缓冲,也可能因为拉取内容的页缓存计入 MEM USAGE。方向无法从单次快照区分。归因结论限定:任务负载(网络拉取、进程数)是内存分层的主要伴生变量;“负载导致内存上升”是推断。
一个不受当前数据支持的简单解释:只用累计块读量解释分层。层 2 与层 3 的块读量区间重叠(188 ~ 290 MB vs 195 ~ 277 MB),但内存相差约 30 MiB;单次累计 I/O 仍不足以排除页缓存、任务年龄或文件访问模式的影响。
与 opencode 对比
参照数据:opencode 沙盒实测 7 个进程(2026-08-12 18:54 top -b -n 1 快照,与 Peri 数据同日采集),内存均值 414.0 MiB(含 1 个僵尸进程)/ 483.0 MiB(6 个有效进程),范围 370.0 ~ 777.7 MiB,均含操作系统。
口径:均含操作系统;Peri 50 容器为 2026-08-12 docker stats 快照;opencode 7 进程为同日 top 快照,空心圆为僵尸进程(0 MiB);Peri 低区圆点纵向微抖仅避免重叠
| 口径 | Peri 数值 | 相对 opencode 有效均值(483.0 MiB) |
|---|---|---|
| 均值 | 92.29 MiB | 约 5.2 倍(约 1/5) |
| 中位数 | 74.09 MiB | 约 6.5 倍(约 1/6) |
| 最低容器 | 49.21 MiB | 约 9.8 倍(约 1/10) |
- 均值口径下 Peri 约为 opencode 的 1/5,中位数口径约 1/6。
- Peri 最重的活跃沙盒(338.8 MiB,57 进程)约为 opencode 有效均值的 70%,仍低于其均值。
- opencode 分布跨度大(370 ~ 778 MiB),且 7 个进程中有 1 个僵尸(RES = 0,进程退出后未回收);Peri 分布集中(49 ~ 339 MiB),无僵尸。
- 两边的活跃度、任务和采样单位不可比。都包含操作系统不代表同口径对比成立;这些比值只描述两个快照样本,不能外推到产品总体。
差距从哪里来仍未验证。 语言运行时、任务负载、容器年龄、页缓存和子进程结构都是候选变量;本实验没有做同任务对照或进程级分解。另有两个快照观察:
- 僵尸进程:opencode 的 7 个进程里 1 个是僵尸(RES = 0);Peri 快照未观察到僵尸。单次未观察到不代表生命周期机制永远不会产生僵尸。
- 快照高点:opencode 样本峰值 777.7 MiB,Peri 样本峰值 338.8 MiB。两边任务不同,而且存活快照观察不到被 OOM 终止的任务,不能据此判断上限是否充足。
证据与推断
支撑”任务模板”推演的证据:
- 层 2 的 22 个容器网络入全部在 13.1 ~ 13.4 MB 内(带宽 300 kB),同一层内 PIDS 全为 10——如果负载是随机分布的,22 个容器不可能收敛到这么窄的带宽。
- 层 3 两档(24.6 ~ 27.5 / 60.4 ~ 68.2 MB)各自档内收敛,档间差距整齐,且档内内存同步收敛。
- 层 1 的 4 个容器网络入
<120 kB,与其余 46 个(>13 MB)断崖式分离,符合”刚创建”与”跑过任务”的二分。
反证与未确认项:
- 块读量在层 2 与层 3 之间无差异(188 ~ 290 vs 195 ~ 277 MB),排除”内存分层 = 读取量分层”的归因。
- PIDS 与内存的相关(0.84)受单点影响:去掉 338.8 MiB(57 PID)的容器后降至 0.66,而网络入流相关反而升至 0.92——“进程数驱动内存”的证据弱于”网络流量驱动内存”。两方向均需更多观测确认,这是推演。
- “任务模板”推断无法从单次快照证伪,也不排除”同档容器恰好在不同时刻跑过不同任务、当前都处于空闲”的解释。
总结
- 该快照中 Peri 沙盒内存均值 92.29 MiB、中位数 74.09 MiB(含操作系统与 peri,512 MiB 限制内)。 46/50 容器低于 105 MiB,44% 位于 67 ~ 74 MiB;这一分布只适用于本次样本。
- 内存随负载阶梯分层,层内几乎不波动。 网络入流量按 13.2 / 25 / 65 MB 三档收敛,对应 67 ~ 74 / 91 ~ 97 / 101 ~ 105 MiB 三档内存——“固定任务模板”的痕迹,这是推断。
- 与 opencode 同口径对比约为其 1/5 ~ 1/6。 483.0 MiB(opencode 有效均值,7 进程实测)对 92.29 MiB(Peri 均值)/ 74.09 MiB(中位数);Peri 最重活跃容器 338.8 MiB 约为 opencode 均值的 70%。
- Rust 原生二进制、debian-slim 镜像和 512 MiB 限制是候选解释,不是本实验确认的因果因素。 需要同任务、同宿主、时间序列和消融对照才能估计各自贡献。
- 负载与内存的相关限定在相关性层面。 内存与网络入流量(0.88)、PIDS(0.84)强相关,但单次快照无法区分”负载导致内存上升”与”任务内容本身占内存”。
这次快照没有回答的问题:基线的上下限随任务类型会变到哪里、页缓存占了多少、僵尸进程在更长时间尺度上会不会出现。下一次快照时再补。