# 性能

用起来的实际感受，实测数字，也包括做得不好的地方：每个数字是怎么测的、在什么机器上测的。

这里是人能感觉到的数字：没下载过的文件多久开始播放，存一个大文件有多快，列一个文件夹要多久。做得不好的地方我们也一起公开，那正是接下来要改的。

> [!NOTE]
> 2026-11-15 在**一台机器**上测得：4 核、15 GB 内存。服务器、块网关、数据库和客户端都在这台机器上，走回环网络，所以**这些数字里没有真实的网络延迟**，反映的是软件本身的开销。换到真实网络，还要加上往返时间和带宽限制。

## 结果

| 做什么 | 结果 | 说明 |
|---|---|---|
| 保存一个 1 GiB 新文件 | 168 MiB/s | CLI `put`，按内容切块，块都是新的 |
| 改了中间 1 MiB 后再存一次 | 1.6 秒 | 只传改动的块；时间主要花在本机重新算哈希 |
| 没下载过的文件，读前 64 KiB | 37 ms | Linux 挂载（FUSE），打开加读取 |
| 再读它末尾附近的 64 KiB | 20 ms | 只取这一处所在的块 |
| 从头读完（没缓存） | 240 MiB/s | 顺序读，来自块网关 |
| 再读一遍（已缓存） | 536 MiB/s | 本机缓存 |
| 列出 5,000 个文件的文件夹 | 132 ms | 挂载里 `ls` |
| 往挂载里写 500 个小文件 | 每秒 133 个 | 写完即返回，后台上传 |
| ……直到 500 个都成为服务器上的版本 | 4.0 秒 | 从第一次写开始算 |
| SDK 保存 1 KiB 文件 | p50 22 ms，p95 27 ms | Python SDK |
| 读回来 | p50 7 ms，p95 8 ms | |
| 按路径查 | p50 3 ms，p95 4 ms | |
| 末尾追加一行 | p50 67 ms，p95 72 ms | `append_text` |

## 做得不好的地方

- **末尾追加一行比重新保存整个小文件还慢 3 倍**（67 ms 对 22 ms）。追加要先查一次文件，再让服务器读出原内容、拼上新的一行、整个重新切块。现在小文件直接整存更快；追加应该只重写最后一块。
- **第二次列同一个文件夹没有变快**（第二次 141 ms）。挂载的目录缓存只有 1 秒，每次 `ls` 都去问服务器。客户端本机已经有整棵树的副本，可以直接用它回答，再靠推送保持更新。
- **改了 1 MiB 后再存 1 GiB 的文件要 1.6 秒**，几乎都花在把整个文件重新读一遍算哈希。挂载里写入时已经知道改了哪些字节，可以只重算受影响的块。

## 自己跑一遍

在仓库的检出目录里，用装了 Docker 的 Linux：

```sh
scripts/dev-up.sh --native && tests/e2e/bench.sh     # BENCH_MB=2048 BENCH_FILES=10000 可以调大
```

它会打印同样的表格，并把原始结果写成 JSON。很多人同时使用时服务器能撑多少（负载测试），见仓库里的 `docs/performance.md`。
