
# 基准测试

基准测试框架会写入稳定的 JSON 和易读的报告。它位于 `bench/`，属于 [storh 仓库](https://github.com/inline0/storh)，自 0.5.2 起便不再包含在 Composer 软件包中，因此下面的 `composer bench*` 命令只能在克隆仓库后运行，并且需要先执行 `composer install`：

```bash
composer bench
composer bench -- --dataset=100000 --engine=doc
composer bench -- --dataset=100000 --engine=log
composer bench -- --dataset=100000 --engine=queue
composer bench -- --dataset=100000 --engine=cache --cache-validation=trust
composer bench -- --dataset=100000 --engine=filter
composer bench -- --dataset=1000000 --engine=uuid
composer bench:jit -- --dataset=1000000 --engine=uuid
composer bench:repeat -- --dataset=100000 --engine=doc --repeat=5
composer bench:repeat -- --dataset=1000000 --engine=filter --repeat=5 --memory-limit=512M
composer bench:range -- --datasets=1000,10000,50000,100000
composer bench:range -- --datasets=100000 --engines=cache --memory-limit=512M
composer bench:compare build/bench-main.json build/bench-current.json
composer bench:gate -- build/bench-main.json build/bench-current.json --threshold=10 --metric=doc.put --metric=log.stream
composer bench:ci
```

## 参考结果

在配备 Apple M1 Pro（16 GB、macOS 26.5、APFS）和 PHP 8.5 CLI 的设备上，关闭 opcache 和 JIT 后，运行该框架三次所得的中位数；每个引擎处理 50,000 条记录：

```bash
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M
```

每次写入都会先刷新并执行 fsync，然后 storh 才会报告已存储，因此写入速率受文件系统限制。读取则使用操作系统页面缓存。速率根据中位数计算；你可以从仓库克隆版运行 `composer bench`，测试自己的硬件。

| DocStore，50k 条记录 | 中位数 |
| ---------------------------------------------- | --------------- |
| `put()`，每条记录一个持久化文件 | 3.3k 条记录/s |
| `putStream()` 批量导入 | 3.7k 条记录/s |
| `importJsonl()` | 3.6k 条记录/s |
| `get()` 点读取，经过 STAT 验证 | 5.6 µs |
| 重新打开现有存储 | 0.26 ms |
| 索引等值查询，`limit(100)` | 1.1 ms |
| 整个集合的索引 `count()` | 41 µs |
| 索引构建，2 个等值字段 + 1 个范围字段 | 52k 条记录/s |
| 完整 `stream()`，并进行 STAT 重新验证 | 103k 条记录/s |
| `exportJsonl()` | 104k 条记录/s |

| SegmentedLog，50k 条记录，16 KB 分段 | 中位数 |
| ---------------------------------------------- | --------------- |
| `put()`，每次追加都执行 fsync | 16k 次追加/s |
| `appendStream()` 批量导入 | 56k 条记录/s |
| 游标从中点读取 100 条记录 | 2.1 ms |
| 时间范围读取 | 1.1 ms |
| 等值 `count()` | 11 µs |
| 对所有封存分段执行 `compact()` | 69k 条记录/s |
| 通过不完整尾部恢复后重新打开 | 218k 条记录/s |

基准测试每隔 16 KB 封存一个分段，以便对分段轮换进行压力测试；默认分段大小为 1 MiB。

| Queue，50k 个任务 | 中位数 |
| ---------------------------------------------- | --------------- |
| `enqueue()`，每个事件都执行 fsync | 22k 个任务/s |
| `claim()` | 23k 个任务/s |
| `complete()` | 23k 个任务/s |
| `enqueueMany()` | 209k 个任务/s |
| `claimMany()` | 467k 个任务/s |
| `completeMany()` | 512k 个任务/s |

| SQL Mirror，SQLite，50k 条记录 | 中位数 |
| ---------------------------------------------- | --------------- |
| 初始 `push()` | 61k 行/s |
| 未发生更改时的 `push()` | 86k 条记录/s |
| `flush()`，100 个 ID | 7.1 ms |
| 对镜像执行索引 SQL `COUNT` | 6.9 ms |
| `rebuild()` | 65k 行/s |
| `pull()` 恢复，每条记录一个持久化文件 | 3.2k 条记录/s |

| 微基准 | 中位数 |
| ---------------------------------------------- | --------------- |
| 缓存的 `get()`，冷启动后转为热缓存（MemoryCache，STAT） | 83 µs / 6.3 µs |
| UUIDv7 生成 | 1.3 µs |
| UUIDv7 验证 | 0.30 µs |
| 内存谓词筛选 | 4.4M 行/s |

覆盖的目标：

- DocStore 写入、流式写入、JSONL 导出/导入、获取、删除、流式读取和索引构建
- 等值索引、复合索引、范围索引、升序和降序范围查询、索引计数、ID 查找以及非索引 DocStore 查询
- SegmentedLog 追加、流式追加、游标读取、时间范围读取、压缩后的时间范围读取、QueryBuilder 游标与 ID 查找、计数、批量追加计数、统计和压缩
- Queue 入队、批量入队、认领、批量认领、完成、批量完成、重新入队
- 对内存行执行 RecordQuery 和 QueryCondition 谓词筛选
- UUIDv7 单调生成、时间戳分布生成、验证和时间戳提取
- 末尾不完整行的恢复
- 使用哈希、stat 或可信验证执行冷缓存与热缓存读取

默认输出路径：

```text
build/bench-current.json
```

范围测试会为每个数据集和引擎写入一个 JSON 文件，路径位于 `build/bench-ranges/` 下。

`bench:gate` 会比较两个基准测试 JSON 文件，如果任何受跟踪指标慢于配置的阈值，就以非零状态退出。如果未传入 `--metric` 值，就会对基线文件中存在的所有指标进行门限检查。

`bench:ci` 是 GitHub Actions 烟雾测试性能回归门限。它会运行一次涵盖所有引擎的 1k 基准测试，将选定的写入/读取/缓存/UUID 指标与已跟踪的 `bench/baselines/ci-1k-all.json` 基线进行比较，并使用较宽松的默认阈值，以避免运行器的正常波动，同时仍能检测到明显的性能回退。

`bench:jit` 会在启用 CLI opcache 和跟踪 JIT 的情况下运行相同的测试框架，并为基准测试进程禁用 Xdebug，使 PHP 能够实际启用 JIT。生成的 JSON 会包含运行时标志，以显示该次运行中 opcache 和 JIT 是否处于启用状态。
