解读 gVisor 性能数据:从 website/performance 基准 CSV 到 runc/runsc 实测对比
【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor
导读
本文以仓库中 website/performance 目录为核心,完整解读 gVisor 官方保留的 14 份基准测试数据(CSV),带你逐项看懂 runc 与 runsc 在启动时间、内存密度、CPU、系统调用、网络、文件系统等维度上的真实差距。同时结合 性能指南 中的"结构性成本(structural cost)与实现成本(implementation cost)"分析框架,以及仓库内 test/benchmarks 的现代基准工具链,说明这些数字从何而来、如何解读,以及如何在你自己的机器上复现同类测试。读完本文,你将具备独立评估 gVisor 沙箱性能模型并设计可复现基准实验的能力。
一、性能数据目录:历史基准的官方存档
website/performance/README.md 是对整个website/performance/目录的说明。其核心信息有三点:
- 该目录存放的是由 benchmark-tools 仓库生成的 CSV 文件;
- 原 Python 版 benchmark-tools 已被移除,功能等价的替代实现在仓库内的 test/benchmarks(Go 语言重写,基于标准库
testing.B); - 未来这些数据会被自动发布到云存储桶并由网站动态加载,届时该静态目录将被删除。
也就是说,这批 CSV 是 gVisor 在特定历史版本、特定测试环境下的基准快照,具有"官方参考数据"的地位,但也是静态存档。目录中共有 14 个 CSV 文件:
| CSV 文件 | 对应基准 | 维度 |
|---|---|---|
| startup.csv | 容器启动时间 | 启动 |
| density.csv | 容器内存占用密度 | 内存 |
| sysbench-cpu.csv | CPU 计算吞吐 | CPU |
| sysbench-memory.csv | 内存访问带宽 | 内存 |
| syscall.csv | 裸系统调用延迟 | 系统调用 |
| redis.csv | Redis 典型操作吞吐 | 系统调用/应用 |
| iperf.csv | 原始网络吞吐 | 网络 |
| applications.csv | node/ruby HTTP 服务 | 网络/应用 |
| httpd100k.csv | Apache 服务 100k 静态文件 | 文件系统/网络 |
| httpd10240k.csv | Apache 服务 10MB 静态文件 | 文件系统/网络 |
| fio.csv | 磁盘顺序/随机 I/O | 文件系统 |
| fio-tmpfs.csv | tmpfs 内存盘 I/O | 文件系统 |
| ffmpeg.csv | 视频转码耗时 | 综合 |
| tensorflow.csv | CNN 训练耗时 | 综合/CPU |
二、CSV 数据格式与指标约定
所有 CSV 均采用扁平的长表结构,前三列语义如下:
runtime:被测容器运行时。仓库数据中仅出现runc(原生容器)与runsc(gVisor),syscall.csv额外包含runsc-kvm(KVM 平台);method:基准方法/场景名,例如startup.empty、density.node、http.node、fio.randread、PING_INLINE等。httpd*.csv的首列是connections(并发连接数);metric:指标名,例如startup_time_ms(毫秒)、memory_usage(字节)、transfer_rate、latency(毫秒)、requests_per_second、bandwidth(字节/秒)、syscall_time_ns(纳秒)、run_time(秒)等;result:实测数值。
以 startup.csv 为例:
runtime,method,metric,result runc,startup.empty,startup_time_ms,1193.10768 runc,startup.node,startup_time_ms,2557.95336 runsc,startup.empty,startup_time_ms,1144.1775可以理解为"runc 启动一个空容器(仅执行true)耗时约 1193ms"这样的句子。注意memory_usage单位为字节、bandwidth单位为字节/秒,阅读时需自行换算。
三、测试方法论:数据是怎么测出来的
解读这些数字前,必须了解 性能指南 中记录的测试前提,否则极易误读:
- 机器环境:Google Compute Engine
n1-standard-4(Broadwell)虚拟机,镜像 Debian GNU/Linux 9(stretch),内核 4.19.0-0,2048GB SSD 持久化启动盘; - 默认平台:除特别说明外,所有 runsc 测试均在ptrace 平台上进行。ptrace 无需硬件虚拟化、兼容性最好,但结构性成本最高,不代表理想场景;指南明确指出大多数场景应使用 Systrap 以获得最佳性能;
- 对照组:
runc代表原生容器基线,runsc是 gVisor 的 OCI 运行时入口; - 数据生成:由 benchmark-tools 仓库产出,即现在仓库内的 test/benchmarks。
指南提出了一个贯穿全文的两分类成本模型,这是理解所有数据的关键:
- 结构性成本(structural cost):由 gVisor 架构决定、无法通过优化轻易消除的成本。例如 Sentry 本身需要额外内存、应用系统调用必须穿越额外的软件层;以及出于安全设计选用 Go 语言实现 Sentry 而带来的取舍;
- 实现成本(implementation cost):gVisor 作为系统调用面的独立实现,某些子系统尚未优化到成熟实现(如内核、runc)的同等水平。典型例子是网络栈仍在演进中、CPU 效率相对较低。这类成本是持续改进的对象。
四、逐项解读基准数据
4.1 启动时间(startup.csv)
| runtime | 场景 | 启动时间(ms) |
|---|---|---|
| runc | startup.empty | 1193.11 |
| runsc | startup.empty | 1144.18 |
| runc | startup.node | 2557.95 |
| runsc | startup.node | 2441.90 |
| runc | startup.ruby | 2530.13 |
| runsc | startup.ruby | 2455.70 |
三个场景分别是:执行true的 Alpine 空容器、加载多个模块并绑定 HTTP 端口的 node 应用(以端口收到首个成功请求计时)、行为类似的 ruby 应用。有意思的是,这份历史数据中runsc 的启动时间与 runc 基本持平甚至略快,这是因为绝大部分开销来自 Docker 本身——空容器的 runc 基线就已高达约 1193ms。指南特别提示:若要规避 Docker 开销,可考虑使用runsc do模式或直接调用 OCI runtime。
4.2 内存占用与密度(density.csv)
| runtime | 场景 | 内存占用(字节) |
|---|---|---|
| runc | density.empty | 4,092,149.76(≈3.9MB) |
| runsc | density.empty | 23,695,032.32(≈22.6MB) |
| runc | density.node | 76,709,888(≈73.2MB) |
| runsc | density.node | 124,076,605.44(≈118.3MB) |
| runc | density.ruby | 45,737,000.96(≈43.6MB) |
| runsc | density.ruby | 106,141,777.92(≈101.2MB) |
| runc | density.redis | 1,055,323,750.4(≈1GB 数据) |
| runsc | density.redis | 1,076,686,028.8(≈1GB 数据) |
测量方法:运行大量容器实例(一般 50 个,redis 为 5 个),统计主机内存前后差值再除以容器数,而不是直接读取 cgroup 的usage_in_bytes——因为某些运行时(非 runc/runsc)不创建独立容器 cgroup。结论清晰:Sentry 带来一个"小且基本固定"的内存增量(空容器约 18.7MB 的差额),但随应用规模增长,相对占比快速下降(redis 场景仅差约 2%)。对于追求高密度的场景(大量低流量容器),这部分固定开销是评估时的首要考量。
4.3 CPU 与内存访问(sysbench-cpu / sysbench-memory / tensorflow)
| 基准 | runc | runsc | 解读 |
|---|---|---|---|
| sysbench CPU(events/s) | 103.62 | 103.21 | 几乎无差异 |
| sysbench 内存(ops/s) | 13098.73 | 13107.44 | 几乎无差异 |
| TensorFlow CNN 训练(s) | 207.11 | 244.47 | 慢约 18% |
指南指出:gVisor不模拟、不干扰应用原生执行 CPU 指令,因此 CPU 密集型负载没有运行时开销——sysbench 的 CPU 事件率数据印证了这一点。内存访问上,页错误等 OS 机制虽然经由 Sentry 翻译,但映射一旦安装,后续访问没有额外开销。TensorFlow 示例(卷积神经网络训练)的耗时差异主要来自容器整体启动与运行时间,且该测试同样基于 ptrace 平台。对于数据处理、机器学习等 CPU 密集型负载,runsc 通常只引入极小开销。
4.4 系统调用延迟(syscall.csv)
| runtime | syscall_time_ns |
|---|---|
| runc | 1939 |
| runsc(ptrace) | 38219 |
| runsc-kvm | 763 |
这是唯一包含 KVM 平台数据的 CSV:测试由自定义二进制执行大量裸系统调用后取平均。ptrace 平台单次系统调用约 38.2μs,是 runc 的约 20 倍;而 KVM 平台仅约 763ns,比 runc 原生还低。这直观展示了平台选择对结构性成本的巨大影响。系统调用开销主要冲击系统调用密集型应用(如高性能数据存储、静态网络服务);应用在用户态做的工作越多,被摊薄的相对影响越小。
4.5 Redis 基准(redis.csv)
| 操作 | runc(req/s) | runsc(req/s) | 相对吞吐 |
|---|---|---|---|
| PING_INLINE | 30525.03 | 14528.55 | ≈47.6% |
| PING_BULK | 30293.85 | 15627.44 | ≈51.6% |
| SET | 30257.19 | 15403.57 | ≈50.9% |
| GET | 30312.21 | 15325.67 | ≈50.6% |
| INCR | 30525.03 | 15269.51 | ≈50.0% |
| LPUSH | 30712.53 | 15172.20 | ≈49.4% |
| RPUSH | 30459.95 | 15117.16 | ≈49.6% |
| LPOP | 30367.45 | 15257.86 | ≈50.2% |
| RPOP | 30665.44 | 15188.33 | ≈49.5% |
| SADD | 30030.03 | 15432.10 | ≈51.4% |
| HSET | 30656.04 | 15163.00 | ≈49.5% |
| SPOP | 29940.12 | 15561.78 | ≈52.0% |
| LRANGE_100 | 24224.81 | 13365.41 | ≈55.2% |
| LRANGE_300 | 14302.06 | 9520.18 | ≈66.6% |
| LRANGE_500 | 11728.83 | 8248.78 | ≈70.3% |
| LRANGE_600 | 9900.99 | 6544.07 | ≈66.1% |
| MSET | 30120.48 | 14367.82 | ≈47.7% |
规律非常典型:单次用户态工作量越小的操作(PING/SET/GET)相对损耗越大,而LRANGE这类在应用内部做更多工作的操作相对损耗更小。Redis 在用户态几乎不做事(读 socket→改数据→写回 socket),是系统调用结构性成本最"吃亏"的场景,指南坦言 redis 很可能是长期具有挑战性的性能场景,但优化平台选择也会有显著改观。
4.6 网络(iperf.csv / applications.csv / httpd*.csv)
iperf 原始吞吐:
| 方向 | runc(MB/s) | runsc(MB/s) |
|---|---|---|
| download | ≈711.9 | ≈610.6 |
| upload | ≈677.0 | ≈459.9 |
测试中指定的运行时作为 iperf 客户端(upload)或服务端(download),另一端始终用原生运行时。
HTTP 应用(applications.csv,25 并发,模板渲染):
| 应用 | 指标 | runc | runsc |
|---|---|---|---|
| node | transfer_rate | 3814.85 | 1615.54 |
| node | latency(ms) | 11 | 27 |
| node | requests_per_second | 885.81 | 375.13 |
| ruby | transfer_rate | 2874.38 | 1382.71 |
| ruby | latency(ms) | 18 | 38 |
| ruby | requests_per_second | 539.97 | 259.75 |
Apache 静态文件服务(httpd100k.csv:单文件 100k;httpd10240k.csv:单文件 10MB,ApacheBench 驱动,指标为 transfer_rate 与 latency(ms)):
| 并发连接 | runtime | 100k 吞吐(KB/s) | 100k 延迟 | 10MB 吞吐(KB/s) | 10MB 延迟 |
|---|---|---|---|---|---|
| 1 | runc | 565.35 | 1ms | 674.05 | 1ms |
| 1 | runsc | 282.84 | 2ms | 243.35 | 2ms |
| 5 | runc | 3260.57 | 1ms | 3089.83 | 1ms |
| 5 | runsc | 832.69 | 3ms | 981.91 | 2ms |
| 10 | runc | 4672.01 | 1ms | 4701.20 | 1ms |
| 10 | runsc | 1095.47 | 4ms | 1135.08 | 4ms |
| 25 | runc | 4964.14 | 2ms | 5021.36 | 2ms |
| 25 | runsc | 961.03 | 12ms | 963.26 | 12ms |
指南明确说明:网络性能绝大部分受实现成本约束,且 gVisor 的网络栈正在快速改进;httpd这种在热路径上执行大量文件操作(且所有请求读同一文件、存在内部串行点)的基准,同时叠加了 VFS 实现成本与网络栈问题,结果"predictably poor"(可预期的差),属于最能体现当前实现瓶颈的极端场景。
4.7 文件系统(fio.csv / fio-tmpfs.csv / ffmpeg.csv)
fio(sync 引擎,磁盘):
| 场景 | runc(MB/s) | runsc(MB/s) |
|---|---|---|
| read | ≈240.6 | ≈240.7 |
| write | ≈436.6 | ≈411.8 |
| randread | ≈5.0 | ≈4.2 |
| randwrite | ≈102.8 | ≈65.9 |
fio(sync 引擎,tmpfs):
| 场景 | runc(MB/s) | runsc(MB/s) |
|---|---|---|
| read | ≈4044.1 | ≈2416.3 |
| write | ≈2889.2 | ≈1151.5 |
| randread | ≈1164.8 | ≈65.7 |
| randwrite | ≈997.6 | ≈64.2 |
ffmpeg 转码 27MB 视频总耗时:runc 82.00s vs runsc 88.24s。
解读要点:
- 原始磁盘 I/O 上 gVisor 无显著结构性开销:fio.csv 中顺序读写与 runc 几乎持平,因为磁盘本身成为瓶颈;
- tmpfs 场景放大了 VFS 实现成本:sandbox 内部的 tmpfs 不受磁盘瓶颈约束,操作成本主要由内存拷贝与 VFS 层决定,顺序读写约慢 40%~60%,随机读写差距更大(约 6%~16% 的吞吐保留);
- 指南指出文件系统方面同样实现成本占主导(内部 VFS 实现需要改进),属于快速改进中的领域;
- ffmpeg 这类"磁盘 I/O 为主 + 混合计算"的负载中,文件系统开销被摊薄,整体仅慢约 7.6%。
五、在源码中印证基准的实现
数据与结论在仓库源码中有对应实现可查证:
- 基准工具本身:test/benchmarks/README.md 说明了 Go 版基准的编写范式——使用
testing.B、通过 dockerutil 启动容器、用harness.GetMachine()声明所需机器数、以b.ReportMetric()上报自定义指标; - fio 基准:test/benchmarks/fs/fio_test.go 中可见
BenchmarkFioWrite/Read/RandWrite/RandRead覆盖多种块大小(4KB/64KB/1024KB)、I/O 引擎(sync/libaio)、深度与多 job,并在 bind/tmpfs/rootfs 三类挂载上重复测试;测试要求 root 权限用于harness.DropCaches()清理页缓存; - ffmpeg 基准:test/benchmarks/media/ffmpeg_test.go 展示了"尊重
b.N"的典型写法——每次迭代新建容器执行ffmpeg -i video.mp4 -c:v libx264 -preset veryslow output.mp4,计时只覆盖容器运行段; - 其他基准目录:network(httpd/iperf/nginx/node/ruby)、database(redis)、tcp(含 nsjoin/tcp_proxy/xdp 等);
- 基准镜像:Dockerfile 统一存放在 images/benchmarks 下,约定使用显式版本化的包,且不在镜像内写 ENV/CMD(由 API 层
dockerutil.RunOpts传入)。
六、在本机复现基准测试
结合 Makefile 与 test/benchmarks/README.md,可按下述步骤复现(前提:Docker 17.09.0+、仓库构建环境、root 权限):
- 安装 runsc 作为 Docker runtime:
make dev(会将多个 runsc 配置写入/etc/docker/daemon.json,请选择不带 debug 的配置); - 运行单个基准:
make run-benchmark RUNTIME=[RUNTIME_FROM_DAEMON.JSON/runc] BENCHMARKS_TARGETS=path/to/target- 一键多平台对比(systrap、kvm 与原生 runc 同跑):
make benchmark-platforms BENCHMARKS_TARGET=path/to/target- 列出全部可用基准:
bazel query 'attr("tags", ".*gvisor_benchmark.*", //test/benchmarks/...)'Makefile 中可调的关键变量(Makefile):
| 变量 | 默认值 | 作用 |
|---|---|---|
BENCHMARKS_TARGETS | //test/benchmarks/media:ffmpeg_test | 基准目标 |
BENCHMARKS_FILTER | . | 测试套件过滤正则 |
BENCHMARKS_OPTIONS | -test.benchtime=30s | 传给go test的基准参数(如-test.benchtime=1m或-test.benchtime=10x) |
BENCHMARKS_RUNC | true | 是否同时跑 runc 对照 |
BENCHMARKS_PLATFORMS | 空 | 只针对指定平台列表运行 |
BENCHMARKS_UPLOAD | false | 是否将结果解析并上传 BigQuery(配合BENCHMARKS_PROJECT/DATASET/TABLE/SUITE/OFFICIAL) |
BENCHMARKS_PROFILE | 空 | 性能剖析选项,如-pprof-dir=/tmp/profile -pprof-cpu -pprof-heap |
关于性能剖析,注意两点:运行时需以--profile标志启用(该标志会放宽 seccomp 过滤器以便写盘,不建议用于生产);剖析输出位于/tmp/profile,且 runc 不支持剖析。
Go 基准的基本骨架(摘自 test/benchmarks/README.md):
func BenchmarkMyCoolOne(b *testing.B) { machine, err := harness.GetMachine() // check err defer machine.CleanUp() ctx := context.Background() container := machine.GetContainer(ctx, b) defer container.CleanUp(ctx) b.ResetTimer() // Respect b.N. for i := 0; i < b.N; i++ { out, err := container.Run(ctx, dockerutil.RunOpts{ Image: "benchmarks/my-cool-image", Env: []string{"MY_VAR=awesome"}, }, "sh", "-c", "echo MY_VAR") // check err... b.StopTimer() // Do parsing and reporting outside of the timer. number := parseMyMetric(out) b.ReportMetric(number, "my-cool-custom-metric") b.StartTimer() } } func TestMain(m *testing.M) { harness.Init() os.Exit(m.Run()) }写作基准时需遵守三条约定:尊重并随b.N线性扩展(用户可用--benchtime=10x或--benchtime=1m控制迭代);用b.ReportMetric()上报自定义指标;客户端-服务端型基准(如 httpd)把b.N作为客户端容器发起请求数的参数。
七、阅读这些数据的注意事项
- 平台前提:绝大多数 runsc 数据基于 ptrace 平台,这是结构性成本最高的平台。从 syscall.csv 可见,KVM 平台系统调用延迟比 runc 还低;指南建议大多数场景使用 Systrap 以获得最佳性能。因此这批数据不能代表 runsc 在最优平台上的表现;
- 版本与时效:CSV 是历史快照(对应已移除的 Python benchmark-tools 时代,机器为 n1-standard-4/Debian 9 内核 4.19),不代表当前版本性能;网络栈与 VFS 等实现成本正在持续改进;
- 负载匹配:沙箱并非对所有负载都适用——例如可信数据库本就把用户数据置于沙箱内,攻破沙箱并无额外收益,这类场景沙箱价值有限;
- 成本归属:系统调用、内存、密度数据主要体现结构性成本(长期存在但可因平台优化而改善);网络、VFS、tmpfs 随机 I/O 主要体现实现成本(正在被持续优化)。区分二者有助于判断"该不该投入优化、优化空间在哪"。
总结
website/performance 目录的 14 份 CSV 是理解 gVisor 性能模型的一手素材:CPU 与内存访问几乎无开销、密度成本小且固定、系统调用与网络/文件系统是实现改进的主战场、平台选择(ptrace/KVM/Systrap)对结构性成本影响巨大。结合 性能指南 的成本分析框架与 test/benchmarks 的 Go 基准工具链,你既可以读懂官方历史数据,也可以按本文给出的 Makefile 命令在目标硬件上复现并评估适合自身负载的性能结论。
【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考