Skip to content
数据分层

Peri 沙盒内存占用研究

50 个生产环境沙盒容器(debian-slim + peri,512 MiB 限制)的 docker stats 快照分析——22 个容器挤在 67~74 MiB 窄带、内存随负载呈阶梯分层、与 opencode 实测(7 进程均值 414~483 MiB)的对比。

编写于 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 / P9067.5 / 68.7 / 101.0 / 104.7

按区间看:

区间容器数
<60 MiB4
60 ~ 75 MiB22
75 ~ 105 MiB20
105 ~ 150 MiB2
>150 MiB2

50 个容器按内存升序排列的瓷砖图,颜色对应层分组

口径:单次快照;每格一个容器,按内存升序;颜色对应五层;格内为该容器内存值;前 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 刚创建态449.21 ~ 58.03<120 kB0 ~ 63 MB10
层 2 基线态2267.09 ~ 74.2113.1 ~ 13.4 MB188 ~ 290 MB10
层 3 任务态2091.08 ~ 104.716.6 ~ 68.2 MB195 ~ 277 MB10
层 4a 写盘任务2110.2 / 116.926 / 27.4 MB3.6 MB / 344 kB22 / 16
层 4b 密集任务2280.2 / 338.8813 MB / 423 MB1.14 GB / 154 MB20 / 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 与 opencode 沙盒内存分布对比

口径:均含操作系统;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——“进程数驱动内存”的证据弱于”网络流量驱动内存”。两方向均需更多观测确认,这是推演。
  • “任务模板”推断无法从单次快照证伪,也不排除”同档容器恰好在不同时刻跑过不同任务、当前都处于空闲”的解释。

总结

  1. 该快照中 Peri 沙盒内存均值 92.29 MiB、中位数 74.09 MiB(含操作系统与 peri,512 MiB 限制内)。 46/50 容器低于 105 MiB,44% 位于 67 ~ 74 MiB;这一分布只适用于本次样本。
  2. 内存随负载阶梯分层,层内几乎不波动。 网络入流量按 13.2 / 25 / 65 MB 三档收敛,对应 67 ~ 74 / 91 ~ 97 / 101 ~ 105 MiB 三档内存——“固定任务模板”的痕迹,这是推断。
  3. 与 opencode 同口径对比约为其 1/5 ~ 1/6。 483.0 MiB(opencode 有效均值,7 进程实测)对 92.29 MiB(Peri 均值)/ 74.09 MiB(中位数);Peri 最重活跃容器 338.8 MiB 约为 opencode 均值的 70%。
  4. Rust 原生二进制、debian-slim 镜像和 512 MiB 限制是候选解释,不是本实验确认的因果因素。 需要同任务、同宿主、时间序列和消融对照才能估计各自贡献。
  5. 负载与内存的相关限定在相关性层面。 内存与网络入流量(0.88)、PIDS(0.84)强相关,但单次快照无法区分”负载导致内存上升”与”任务内容本身占内存”。

这次快照没有回答的问题:基线的上下限随任务类型会变到哪里、页缓存占了多少、僵尸进程在更长时间尺度上会不会出现。下一次快照时再补。