---
title: "Peri 沙盒内存占用研究"
description: "50 个生产环境沙盒容器（debian-slim + peri，512 MiB 限制）的 docker stats 快照分析——22 个容器挤在 67~74 MiB 窄带、内存随负载呈阶梯分层、与 opencode 实测（7 进程均值 414~483 MiB）的对比。"
---

> Documentation Index
> Fetch the complete documentation index at: https://konghayao.github.io/peri-cool/llms.txt
> Use this file to discover all available pages before exploring further.

# Peri 沙盒内存占用研究

> 这是一次单机、单时点的观察性快照：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 |

![50 个容器按内存升序排列的瓷砖图,颜色对应层分组](/chart-sandbox-dist.svg)

*口径:单次快照;每格一个容器,按内存升序;颜色对应五层;格内为该容器内存值;前 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 |

![任务量(网络入累计)与内存的阶梯对应](/chart-sandbox-net.svg)

*口径:网络入为容器生命周期累计值;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 沙盒内存分布对比](/chart-sandbox-compare.svg)

*口径:均含操作系统;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）强相关，但单次快照无法区分"负载导致内存上升"与"任务内容本身占内存"。

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

Source: https://konghayao.github.io/peri-cool/posts/research/sandbox-memory-usage/index.mdx
