# 阶段 3 图片处理并发基线 测量日期:2026-07-29 默认处理链路:5000 × 3200、16 MP 高压缩 PNG 固定最坏样本,16 次 PNG → WebP 上传 测量方法:服务安全门与客户端并发同时设为 1、2、4、8;记录成功吞吐、p95 和容器 cgroup 峰值内存 ## 本机结果 ### 四格式真实解码验证 本机 Apple M4、Go 1.26.5 对 `InspectFile` 的三次 benchmark 中位值如下。这里的内存是每次操作的 Go heap 分配量;端到端处理的容器峰值见后续并发表。 | 格式与固定样本 | 耗时(ns/op) | 吞吐(MB/s) | Go heap(B/op) | allocs/op | |---|---:|---:|---:|---:| | JPEG,1600 × 1200 | 7,639,101 | 29.13 | 2,924,048 | 17 | | PNG,800 × 600 | 2,871,904 | 3.64 | 1,992,412 | 32 | | WebP,1 × 1 golden | 15,143 | 2.77 | 20,792 | 27 | | GIF,320 × 240、2 帧 | 477,510 | 1.79 | 223,474 | 34 | 四种格式都执行真实解码;GIF 会解码全部帧并把总帧像素计入上限。该基准可用 `go test ./internal/platform/processor -run '^$' -bench BenchmarkInspectFormats -benchmem -count 3` 复现。 ### 16 MP 主动 PNG → WebP 转换 #### macOS arm64 主机,linux/arm64 原生容器 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.621 | 1,641.7 | 390,234,112 | | 2 | 1.166 | 1,763.5 | 635,396,096 | | 4 | 2.197 | 1,855.3 | 1,056,608,256 | | 8 | 3.305 | 2,457.0 | 1,877,291,008 | #### macOS arm64 主机,linux/amd64 OrbStack 模拟容器 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.466 | 2,168.3 | 493,858,816 | | 2 | 0.883 | 2,342.6 | 671,985,664 | | 4 | 1.671 | 2,417.1 | 1,065,037,824 | | 8 | 2.522 | 3,279.0 | 1,959,501,824 | ### 16 MP GitHub 原生 runner 证据:[Verify run 30461966181](https://github.com/Willxup/imagesilo/actions/runs/30461966181),对应提交 `27c8a2c97a8889f62994d55fd4f266176a158377`。原生镜像构建、完整 smoke 和 1/2/4/8 主动转换 benchmark 均通过。 #### 原生 linux/arm64 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.348 | 2,878.0 | 396,300,288 | | 2 | 0.694 | 2,908.5 | 626,872,320 | | 4 | 1.301 | 3,094.0 | 1,059,647,488 | | 8 | 1.298 | 6,299.1 | 1,839,075,328 | #### 原生 linux/amd64 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.335 | 3,008.8 | 422,658,048 | | 2 | 0.654 | 3,067.6 | 623,267,840 | | 4 | 0.897 | 4,493.0 | 1,091,997,696 | | 8 | 0.890 | 9,404.9 | 1,823,301,632 | ### 阶段 7 并发 1 内存分类 证据:[Verify run 30479364047](https://github.com/Willxup/imagesilo/actions/runs/30479364047),对应提交 `b866eebcd411a38fba2365786a1fa05ed4cc443d`。两个原生架构仍各执行 16 次、并发 1 的 16 MP PNG → WebP;阶段 7 额外记录正式输出总量、结束匿名内存和结束文件页缓存。 | 架构 | 吞吐(次/秒) | p95(ms) | 16 个输出(bytes) | 结束匿名内存(bytes) | 结束文件页缓存(bytes) | cgroup 峰值(bytes) | |---|---:|---:|---:|---:|---:|---:| | amd64 | 0.342 | 2,951.1 | 89,923,072 | 86,945,792 | 91,430,912 | 396,574,720 | | arm64 | 0.348 | 2,881.4 | 89,923,072 | 101,023,744 | 91,430,912 | 396,828,672 | 结束文件页缓存与 16 个正式 WebP 的总量几乎相同,属于 Linux 可回收文件缓存,不是进程匿名堆。持续工作集应看 87–101 MB 的匿名内存;cgroup 峰值仍包含单次编码瞬时内存和此前输出文件的页缓存,不能直接等同为“单请求进程内存”。 ### 16 MP 默认原样保存 字节变更开关全部关闭时,并发 1 的 arm64 p95 为 198.2 ms、峰值 86,446,080 bytes;amd64 模拟 p95 为 269.2 ms、峰值 153,608,192 bytes。16 个顺序上传的吞吐分别为 5.08 次/秒和 3.72 次/秒。 并发 2 的对照值为:arm64 p95 242.7 ms、峰值 127,074,304 bytes;amd64 模拟 p95 363.7 ms、峰值 176,701,440 bytes。默认路径只做严格解码校验、缩略图和原子落盘,不执行 WebP 编码。 ### 外部共享主机受控原生 amd64 共享服务器为 4 核 x86_64、9.5 GiB 内存。测试容器硬限制为 1 CPU、1 GiB,服务并发 1,每种模式只执行 4 次 16 MP 上传;测试结束后临时容器、镜像和 `/var/tmp` 数据均已删除。 | 模式 | 吞吐(次/秒) | p95(ms) | 结束内存(bytes) | 峰值内存(bytes) | |---|---:|---:|---:|---:| | 默认原样保存 | 1.063 | 934.0 | 38,424,576 | 70,332,416 | | PNG → WebP | 0.146 | 6,882.7 | 97,079,296 | 304,029,696 | 这里的延迟受到 1 CPU 硬限制,不能与未限速 runner 横向比较;内存结果直接证明默认并发 1 在真实 amd64 共享服务器上保持轻量,主动转换也能稳定运行在 1 GiB 容器边界内。 阶段 7 又在同一外部 amd64 共享主机用提交 `b866eeb` 构建发布候选并连续运行 600 秒。构建器硬限制为 1 CPU、1 GiB,完成后立即删除;运行容器硬限制为 0.5 CPU、256 MiB、并发 1,共完成 118 轮串行 health/readiness/标准 URL/别名哈希校验。运行 cgroup 当前内存 `14,135,296` bytes、峰值 `35,938,304` bytes、结束匿名内存 `8,814,592` bytes;goroutine `10 → 8`、文件描述符 `10 → 10`,SIGTERM 正常退出。 ### 40 MP 边界探针(已不再作为默认) 早期默认上限 40 MP 与轻量目标不符;下列结果仅保留为边界问题的发现证据。默认像素上限现为 16 MP,最终默认决策使用后续 16 MP 数据。 #### macOS arm64 主机,linux/arm64 原生容器 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.109 | 9,299.5 | 1,022,279,680 | | 2 | 0.207 | 9,989.3 | 1,478,410,240 | | 4 | 0.400 | 10,205.3 | 2,843,156,480 | | 8 | 0.565 | 14,179.5 | 5,236,785,152 | #### macOS arm64 主机,linux/amd64 OrbStack 模拟容器 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.080 | 12,934.5 | 1,012,928,512 | | 2 | 0.155 | 13,468.9 | 1,676,865,536 | | 4 | 0.295 | 13,505.9 | 2,806,001,664 | | 8 | 0.421 | 19,424.1 | 5,408,296,960 | amd64 数字当前只是本机模拟反馈,不作为原生阶段门证据。以上多档并发结果作为阶段 3 的一次性选型证据保留;当前 GitHub Actions 只在官方原生 amd64 与 arm64 runner 上复测默认并发 1,并把原始 JSON 写入任务摘要,不再持续消耗资源重复测试 2/4/8。 ## 40 MP GitHub 原生 runner 边界探针 证据:[Verify run 30458478114](https://github.com/Willxup/imagesilo/actions/runs/30458478114),对应提交 `04f3184429485e9ec98fa87b138ba637e254158d`。`quality`、原生 arm64/amd64 镜像构建、完整 smoke 和全部 benchmark 均通过。 ### 原生 linux/arm64 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.054 | 18,548.0 | 1,196,453,888 | | 2 | 0.109 | 18,503.9 | 1,614,176,256 | | 4 | 0.212 | 18,920.9 | 2,974,887,936 | | 8 | 0.212 | 37,962.6 | 4,844,515,328 | ### 原生 linux/amd64 | 并发 | 吞吐(次/秒) | p95(ms) | 峰值内存(bytes) | |---:|---:|---:|---:| | 1 | 0.060 | 16,863.6 | 1,265,750,016 | | 2 | 0.118 | 17,023.4 | 1,922,170,880 | | 4 | 0.156 | 25,684.4 | 2,837,225,472 | | 8 | 0.156 | 51,666.9 | 4,824,039,424 | ## 决策 默认像素上限选择 16 MP,覆盖常见 10–15 MP 大图并留出余量。更高像素上限必须作为单独的部署选择重新 benchmark,不再是新安装或升级后的默认安全边界。 默认 `processingConcurrency` 选择 1。默认原样保存的本机峰值约 86–154 MB,受限原生 amd64 服务器峰值约 70 MB;主动 WebP 转换的阶段 7 原生 runner cgroup 峰值约 397 MB,其中结束时约 91 MB 是与正式输出总量吻合的可回收文件页缓存,结束匿名内存为 87–101 MB。并发 2 的一次性选型数据会把峰值叠加到约 623–627 MB,因此不作为默认值。批量选择仍由浏览器本地队列完成。 最坏样本同时暴露并修正了早期 6 MP 探针没有发现的内存问题:生产真实解码校验不再把整图复制到 Go heap,而是让 libvips 流式求值;无跨请求复用价值的 libvips 操作缓存设为 0;每次图片处理结束后归还 glibc 保留的空闲原生页。修正前并发 1 连续 16 次的结束内存约 1.9 GB、峰值约 2.2 GB;修正后两次上传结束约 108 MB,16 次上传结束值主要由 16 个正式 WebP 文件的可回收页缓存构成,不再存在匿名原生堆持续累积。 共享服务器专项处理 benchmark 默认限制为 0.5 CPU、1 GiB、并发 1 和 4 次请求;发布候选连续运行验收默认限制为 0.5 CPU、256 MiB、并发 1。只有在独占测试环境中才应显式提高这些值。 安全门是无队列门:占满时上传立即返回 `503 Service Unavailable` 和 `Retry-After: 1`。当前 GitHub benchmark 在默认并发 1 下要求 16 次请求全部成功且容器不退出;满载拒绝语义另由 HTTP 集成测试与 race 测试覆盖。脚本仍允许开发者通过 `CONCURRENCIES` 显式执行专项多并发测量,但 CI 不会自动运行。 ## 复现 ```bash IMAGE=imagesilo:phase3-probe \ PLATFORM=linux/arm64 \ PORT=18086 \ BENCH_SUFFIX=arm64-local \ bash scripts/processing-benchmark.sh ```