餐巾纸数学
本项目的目标是收集软件、数字和技术,以便从第一性原理出发快速估算系统的预期性能。例如,读取 1 GB 内存需要多长时间?通过组合这些资源,你应该能够回答一些有趣的问题,例如:对于一个具有 100,000 RPS 的应用程序,你应该预期支付多少日志存储成本?
学习这项技能的最佳入门途径是通过我在 SRECON 上的演讲。
在计算机这一宏大领域中练习餐巾纸数学的最佳方式是解决你自己的问题。次佳方式是订阅这封通讯,你将每隔几周收到一个问题用于练习。随着你对这些技术的熟练程度提高,解决每个问题应该只需要几分钟。
用于练习的问题存档在这里。解决方案将在下一封通讯中公布。
数字
以下是为便于记忆而取整的数字,而非虚假的精确值。
本仓库当前可在单主机上刷新的行,已于 2026 年 3 月 8 日在最新的 GCP c4-standard-48-lssd 实例上重新测量并重新验证
(Intel Xeon 6985P-C,48 vCPU / 24 物理核心,180 GB RAM,Ubuntu 22.04.5
LTS)。
注 1: 部分吞吐量和延迟数值不一致,这是为了便于计算而有意为之。
注 2: 请对这些数值持保留态度。例如,对于 I/O,fio 是
最先进的工具。随着我不断学习以提高准确性以及硬件的进步,我会持续更新这些数值。
| 操作 | 延迟 | 吞吐量 | 1 MiB | 1 GiB |
|---|---|---|---|---|
| 顺序内存读写 (64 字节) | 0.5 ns | |||
| ├ 单线程 | 20 GiB/s | 50 μs | 50 ms | |
| ├ 多线程 | 200 GiB/s | 5 μs | 5 ms | |
| 网络 同可用区 | 10 GiB/s | 100 μs | 100 ms | |
| ├ VPC 内部 | 10 GiB/s | 100 μs | 100 ms | |
| ├ VPC 外部 | 3 GiB/s | 300 μs | 300 ms | |
| 哈希,非加密安全 (64 字节) | 10 ns | 5 GiB/s | 200 μs | 200 ms |
| 随机内存读写 (64 字节) | 20 ns | 3 GiB/s | 300 μs | 300 ms |
快速序列化 [8] [9] † | N/A | 1 GiB/s | 1 ms | 1s |
快速反序列化 [8] [9] † | N/A | 1 GiB/s | 1 ms | 1s |
| 系统调用 | 300 ns | N/A | N/A | N/A |
| 哈希,加密安全 (64 字节) | 100 ns | 1 GiB/s | 1 ms | 1s |
| 顺序 SSD 读取 (8 KiB) | 1 μs | 8 GiB/s | 100 μs | 100 ms |
上下文切换 [1] [2] | 10 μs | N/A | N/A | N/A |
| 顺序 SSD 写入,-fsync (8KiB) | 2 μs | 3 GiB/s | 300 μs | 300 ms |
| TCP 回显服务器 (32 KiB) | 50 μs | 500 MiB/s | 2 ms | 2s |
| 随机 SSD 读取 (8 KiB) | 100 μs | 70 MiB/s | 15 ms | 15s |
解压缩 [11] | N/A | 1 GiB/s | 1 ms | 1s |
压缩 [11] | N/A | 500 MiB/s | 2 ms | 2s |
| 排序 (64 位整数) | N/A | 500 MiB/s | 2 ms | 2s |
| 代理: Envoy/ProxySQL/Nginx/HAProxy | 50 μs | ? | ? | ? |
| 同一区域内的网络 | 250 μs | 2 GiB/s | 500 μs | 500 ms |
| 同可用区/VPC 内的高级网络 | 250 μs | 25 GiB/s | 50 μs | 40 ms |
| 顺序 SSD 写入, +fsync (8KiB) | 300 μs | 30 MiB/s | 30 ms | 30s |
| {MySQL, Memcached, Redis, ..} 查询 | 500 μs | ? | ? | ? |
序列化 [8] [9] † | N/A | 100 MiB/s | 10 ms | 10s |
反序列化 [8] [9] † | N/A | 100 MiB/s | 10 ms | 10s |
| 顺序 HDD 读取 (8 KiB) | 10 ms | 250 MiB/s | 2 ms | 2s |
| 随机 HDD 读取 (8 KiB) | 10 ms | 0.7 MiB/s | 2 s | 30m |
| Blob 存储 GET, if-not-match 304 | 30 ms | |||
| Blob 存储 GET, 1 连接 (128KiB) | 80 ms | 100 MiB/s | 10 ms | 10s |
| Blob 存储 GET, n 连接 (偏移量) | 80 ms | NW 限制 | ||
| Blob Storage LIST | 100 ms | |||
| Blob Storage PUT, 1 conn (128KiB) | 200 ms | 100 MiB/s | 10 ms | 10s |
| Blob Storage PUT, n conn (multipart) | 200 ms | NW limit | 10 ms | 10s |
区域间网络 [6] | Varies | 25 MiB/s | 40 ms | 40s |
| 北美中部 <-> 东部网络 | 25 ms | 25 MiB/s | 40 ms | 40s |
| 北美中部 <-> 西部网络 | 40 ms | 25 MiB/s | 40 ms | 40s |
| 北美东部 <-> 西部网络 | 60 ms | 25 MiB/s | 40 ms | 40s |
| 欧洲西部 <-> 北美东部网络 | 80 ms | 25 MiB/s | 40 ms | 40s |
| 欧洲西部 <-> 北美中部网络 | 100 ms | 25 MiB/s | 40 ms | 40s |
| 北美西部 <-> 新加坡网络 | 180 ms | 25 MiB/s | 40 ms | 40s |
| 欧洲西部 <-> 新加坡网络 | 160 ms | 25 MiB/s | 40 ms | 40s |
†: “快速序列化/反序列化”通常是一种简单的线协议, 仅转储字节,或是一个极其高效的环境。通常标准的 序列化,例如 JSON,属于较慢的类型。我们将两者都包含在此处, 因为序列化/反序列化是一个非常、非常广泛的主题,其性能特征 因数据和实现的不同而存在极大差异。
对于活跃的 Criterion 套件,运行 ./run --bench napkin_math 以获取
正确的优化级别和 Linux 调优。在调试模式下编译时,
你无法获得正确的数值。该包装器内部已经使用了 sudo。
在受限的云镜像上,在调用它之前
运行一次 sudo sysctl -w kernel.perf_event_paranoid=-1。你可以通过添加新的套件
并填补空白来帮助这个项目。
注意: 当前的基准测试路径是 benches/ 中的 Criterion.rs。
src/main.rs 仍然是较旧的临时测试框架,并且是那些尚未完全迁移和重新验证的基准测试的权威来源。
当前的 Criterion 套件现在包括 blob_storage、memory_read、
memory_random、hash、syscall、sort、serialization、compression、
以及 compressed_memory_read。当前的 SSD 数据行是从较旧的
测试框架中刷新的,其中 NAPKIN_BENCH_FILE 指向 RAID0 本地 SSD 挂载点。
compressed_memory_read Criterion 基准测试是一个 BitPacker 整数解包
微基准测试;不应使用它来重写上述通用的 [11]
压缩/解压缩数据行。新的 serialization 和
compression Criterion 组是特定于工作负载的,尚未接入
上述通用的 README 数据行。
新的 blob_storage Criterion 组是可选启用且需要凭据的:设置
NAPKIN_GCS_BUCKET 和/或 NAPKIN_S3_BUCKET。GCS 和 S3 路径现在
都使用 AWS S3 SDK。GCS 端与 GCS XML 互操作端点通信,
需要 NAPKIN_GCS_ACCESS_KEY 加上 NAPKIN_GCS_SECRET_KEY。S3 端
使用 NAPKIN_S3_PROFILE 中的本地 AWS 配置文件(默认 tpuf-test)加上
NAPKIN_S3_REGION(默认 us-west-2)。
并发的 get_offsets 和 put_multipart 路径明确地扇出到
多线程 Tokio 运行时,因此当范围数量/对象数量足够高时,它们可以接近主机 NIC 限制。2026 年 3 月 9 日,同区域
1 GiB 单流 GET 在 S3 上达到了约 95 MiB/s(m6id.12xlarge
在 us-west-2a 中),在 GCS XML 上达到了约 190-200 MiB/s
(c4-standard-48-lssd 在 us-central1-c 中)。在相同的机器上,显式的
并发范围 GET 在 S3 上达到了约 2.0 GiB/s,在
GCS 上达到了约 4.9 GiB/s,而分块 PUT 在 S3 上达到了约 1.8-1.9 GiB/s,在
3.3 GiB/s 在 GCS 上。AWS 自身的 S3 指南建议在饱和 10 Gbps 实例时,为每个并发请求预留约
85-90 MB/s 的预算,这与实测的 S3 单流结果非常接近。上述旧的 500 MiB/s
单流 blob GET 行在两家提供商上均未能复现,因此已下调为保守的通用 100 MiB/s。当前的
blob 存储工作重新验证了吞吐量,远多于首字节延迟;
上述的 50 ms / 150 ms 延迟单元格在添加专门的小对象 / 首字节时间探针之前,仍应视为粗略的
启发式值。
src/bin/s3_latency.rs 中现在有一个专门的小对象延迟探针。
运行 ./script/blob-latency s3 或 ./script/blob-latency gcs 以扫描
get、put、if_none_match 和 put_if_match,跨越默认的
8 KiB .. 8 MiB 大小阶梯,除了 if_none_match,它现在默认为一行
128 KiB,因为其延迟在小尺寸扫描中基本保持平坦。
当你想要更小或
特定提供商的运行时,覆盖 NAPKIN_BLOB_LATENCY_OPS、
NAPKIN_BLOB_LATENCY_SIZES 和
NAPKIN_BLOB_LATENCY_IF_NONE_MATCH_SIZES。延迟探针现在默认每行墙钟
预算为 300 秒,收集在该时间内能容纳的尽可能多的样本。
使用 NAPKIN_BLOB_LATENCY_ROW_SECONDS 进行调优;可选地,使用
NAPKIN_BLOB_LATENCY_SAMPLE_CAP(或旧的 NAPKIN_BLOB_LATENCY_SAMPLES
别名)进行限制,当你想要更短的计数限制实验时。
list 操作现在默认播种一个更大的命名空间(100k 个键),并通过 start_after 测量每个样本一个随机化的 1000-键页面,
而不是反复扫描相同的小固定前缀。使用
NAPKIN_BLOB_LATENCY_LIST_NAMESPACE_KEYS、
NAPKIN_BLOB_LATENCY_LIST_KEYS 和
NAPKIN_BLOB_LATENCY_LIST_SEED_CONCURRENCY 调整该形状。如果你还想要旧的
整个命名空间遍历,请将 list_full_scan 添加到 NAPKIN_BLOB_LATENCY_OPS。
对于对齐的范围读取延迟,./script/blob-random-range-latency 测量
128 KiB 对齐的形状,而 ./script/blob-random-range-latency-8m
测量同一 128 x 1 GiB 对象池上 8 MiB 对齐的形状。
./script/blob-random-range-latency-8m-multipart 通过多部分上传使用 8 MiB 个部分来播种这些 1 GiB 源
对象,然后测量针对这些多部分边界的 8 MiB 对齐
读取。
memory_read 现在在 Criterion 中输出显式的 No SIMD 和 SIMD 变体,
但 README 有意将它们合并为一行单线程和一行
多线程,以便于记忆。
我意识到该套件存在一些低效之处。我打算提升我在这方面的技能, 以确保这些数字是你在生产环境中可能榨取出的性能上限。我认为其中任何一项 与真实值的偏差超过 2-3 倍的可能性极低,这对大多数用户来说不应构成问题。
成本数字
应在各云服务商之间保持一致的大致数字。
| 项目 | 数量 | $ / 月 | 1年承诺 $ /月 | 抢占式 $ /月 | 按小时抢占式 $ |
|---|---|---|---|---|---|
| CPU | 1 | $15 | $10 | $2 | $0.005 |
| GPU | 1 | $5000 | $3000 | $1500 | $2 |
| 内存 | 1 GB | $2 | $1 | $0.2 | $0.0005 |
| 存储 | |||||
| ├ 仓库存储 | 1 GB | $0.02 | |||
| ├ 对象存储 (S3, GCS) | 1 GB | $0.02 | |||
| ├ 本地 HDD | 1 GB | $0.05 | |||
| ├ 临时 SSD | 1 GB | $0.08 | $0.05 | $0.05 | $0.07 |
| ├ 区域 HDD | 1 GB | $0.1 | |||
| ├ 本地 SSD | 1 GB | $0.2 | |||
| ├ 区域 SSD | 1 GB | $0.35 | |||
| 网络 | |||||
| ├ 同可用区 | 1 GB | $0 | |||
| ├ Blob | 1 GB | $0 | |||
| ├ Ingress | 1 GB | $0 | |||
| ├ L4 LB | 1 GB | $0.008 | |||
| ├ Inter-Zone | 1 GB | $0.01 | |||
| ├ Inter-Region | 1 GB | $0.02 | |||
| ├ Internet Egress † | 1 GB | $0.1 | |||
| CDN Egress | 1 GB | $0.05 | |||
| CDN Fill ‡ | 1 GB | $0.01 | |||
| Warehouse Query | 1 GB | $0.005 | |||
| Logs/Traces ♣ | 1 GB | $0.5 | |||
| Metrics | 1000 | $20 | |||
| EKM Keys | 1 | $1 |
† 这指的是离开云提供商的网络流量,例如从 GCP 向 S3 发送数据,或从 AWS 向客户端发送 HTML 的出口网络流量。
‡ 每次缓存填充都会产生额外的费用,其成本接近 blob 存储的写入成本(见下文)。
7 这是少数日志提供商之间的标准定价,但例如 Datadog 定价 不同,对摄入的日志收取 $0.1,并额外收取 $1.5 用于 7 天保留。
此外,对于 blob 存储(S3/GCS/R2/...),你会按每次读/写操作收费(文件数量较少且较大时更便宜):
| 1M | 1000 | |
|---|---|---|
| 读取 | $0.4 | $0.0004 |
| 写入 | $5 | $0.005 |
| EKM 加密 | $3 | $0.003 |
压缩比
这些数据来源于几个来源。 [3] [4] [5] 请注意,压缩速度(但通常不是压缩比)会根据算法和压缩级别(以速度换取压缩率)相差一个数量级。
我通常粗略估计,压缩比每增加 x 倍,性能会下降 10 倍。例如,我们可以在 英文维基百科上获得 2 倍的压缩比,速度约为 200 MiB/s,3 倍时约为 20MiB/s,4 倍时为 1MB/s。
| 内容 | 压缩比 |
|---|---|
| HTML | 2-3x |
| 英语 | 2-4x |
| 源代码 | 2-4x |
| 可执行文件 | 2-3x |
| RPC | 5-10x |
| SSL | -2% [10] |
技巧
- 不要过度复杂化。 如果你的计算基于超过 6 个假设,你很可能把它变得比应有的更复杂。
- 保留单位。 它们是良好的校验手段。 Wolframalpha 在需要帮助转换例如 KiB 到 TiB 时 提供了出色的支持。
- 使用指数进行计算。 许多粗略估算
仅使用系数和指数,例如
c * 10^e。你的目标是 在数量级上正确——那只是e。c重要得多 少。只关注单位数系数和指数会让 在餐巾纸上计算容易得多(更不用说避免了写所有的零)。 - 执行费米分解。 写下你可以猜测的内容,直到你 能够开始暗示答案。当你想知道日志存储的成本时, 你会想知道一行日志有多大,每秒有多少行, 那要花多少钱,等等。
资源
[1]: https://eli.thegreenplace.net/2018/measuring-context-switching-and-memory-overheads-for-linux-threads/[2]: https://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html[3]: https://cran.r-project.org/web/packages/brotli/vignettes/brotli-2015-09-22.pdf[4]: https://github.com/google/snappy[5]: https://quixdb.github.io/squash-benchmark/[6]: https://dl.acm.org/doi/10.1145/1879141.1879143[7]: https://en.wikipedia.org/wiki/Hard_disk_drive_performance_characteristics#Seek_times_&_characteristics[8]: https://github.com/simdjson/simdjson#performance-results[9]: https://github.com/protocolbuffers/protobuf/blob/d20e9a92/docs/performance.md[10]: https://www.imperialviolet.org/2010/06/25/overclocking-ssl.html[11]: https://github.com/inikep/lzbench- 如何在 Linux 上获得一致的基准测试结果?. 这是一份关于各种 Kernel 和 CPU 功能的优秀汇编,用于在可靠的 基准测试中进行切换,例如 CPU 亲和性、禁用 turbo boost 等。它还包含 关于基准测试中适当统计方法的资源。
- LLVM 基准测试技巧. 在 专用 CPU、禁用地址空间 随机化等方面与上述内容相似。
- Top-Down 性能分析
方法.
一篇关于使用
toplev查找瓶颈的有用文章。这对于 我们这里的基准测试套件特别有用,以确保程序是 正确编写(我尚未将它们通过此流程,但计划这样做)。 - Godbolt 的编译器浏览器。一个用于比较 Rust 与例如使用 Clang/GCC 的 C 语言之间汇编代码的绝佳资源。
- cargo-show-asm。一个允许反汇编函数的 Cargo 扩展。不幸的是,对闭包的支持略显不足,这需要一些重构。
- Agner 的汇编 指南。一个关于编写最优汇编代码的出色 资源,对于检查我们套件中各个函数的低效之处将非常有用。
- Agner 的指令 表。一个关于各种指令预期吞吐量的详尽 资源,有助于检查汇编代码。
- halobates.de。由
toplev的作者提供的关于底层 性能的有用资源。 - Systems Performance(书籍)。一本关于分析系统性能、查找瓶颈和理解操作系统的绝佳书籍。
- io_uring。最佳总结,它链接到许多 资源。
- How Long Does It Takes To Make a Context Switch
- Integer Compression Comparisons
- Files are hard