完成查询优化:谓词下推、投影下推和常量折叠,配置好了benchmark

This commit is contained in:
LIm1tZz7 committed 2026-09-21 23:44:37 +08:00
1 parent cae66f0731
commit 0791fe0446
14 files changed
+771 -7

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
# ChuSQL 基准测试
这个目录放**性能基准测试**,和 `chusql-engine`、`chusql-storage` 的功能代码分开,
因为它回答的问题不一样:功能代码回答"对不对",基准测试回答"快不快、快多少"。
```
benchmark/
├── README.md 本文件:怎么跑、测什么、结果怎么解读
├── chusql-engine/ Haskell 查询层:优化器开/关的对比
│ ├── stack.yaml 把 chusql-engine 作为本地包引入
│ ├── chusql-bench.cabal
│ └── src/Main.hs
└── chusql-storage/ Rust 存储层:预留(见该目录下的 README)
```
---
## 怎么跑
```bash
cd benchmark/chusql-engine
stack run # 默认 users=500 行、orders=500 行
stack run -- 1000 1000 0.5 # 也可以自己指定:users 行数、orders 行数、每批目标秒数
```
第一次跑要编译,之后很快。整个默认跑完约 5 秒。
---
## 测什么
四个用例,**每个都跑两遍**:一遍走"翻译出来的原始工序单",一遍走"优化后的工序单"。
两次的结果行数会被打印出来互相对照,**不一致会当场标出来**。
| 用例 | 想看什么 |
| ---- | -------- |
| `JOIN` + `WHERE u.age > 90` | 谓词下推的主场:条件能筛掉约 90% 的行 |
| `JOIN`(无 WHERE) | 只剩投影下推在起作用,收益应该温和 |
| `JOIN` + `WHERE` + `ORDER BY` + `LIMIT` | 规则叠加时的综合效果 |
| 单表 `WHERE age > 90` | 对照组:没有下推空间,优化器只剩固定开销 |
---
## 计时怎么做(两个坑都踩过了)
**坑一:Haskell 是惰性的,不强制求值就等于没测。**
如果只写 `let r = evalRelOp db plan`,`r` 根本没被算出来,测到的是"定义一个变量"的时间。
所以每次必须把结果**算到底**(本项目里是逐行把列数累加起来当校验和)。
**坑二:Windows 上 CPU 时钟的精度只有约 15.6 毫秒。**
优化后的查询只要几毫秒,直接测会得到 `0.0 ms` 这种没意义的数字。
所以计时用的是**自适应批量**:把同一个用例连续跑 `k` 次测一整批,`k` 不断翻倍,
直到一批累计耗时超过目标值(默认 0.3 秒),再折算成单次耗时。
这样时钟精度的误差会被 `k` 摊薄,几毫秒的用例也能测准。
程序会打印实际用了多大的批次(例如"优化后 64 次/批")。
跑正式计时之前还会**预热两遍**,避免把首次构造表数据的开销算进去。
---
## 实测结果
**环境**:Windows,GHC 9.10.3,`-O2`
**数据**:`users` 500 行(`id / name / age`,`age` 只有 0-99 共 100 种取值),
`orders` 500 行(`id / user_id / product`),两表配对共 25 万对。
| 查询 | 结果行数 | 未优化 | 优化后 | 加速 |
| ---- | -------- | ------ | ------ | ---- |
| `JOIN` + `WHERE u.age > 90` | 45 | 125.0 ms | **7.1 ms** | **17.7x** |
| `JOIN`(无 WHERE) | 500 | 132.8 ms | 105.5 ms | 1.3x |
| `JOIN` + `WHERE` + `ORDER BY` + `LIMIT 10` | 10 | 171.9 ms | **10.3 ms** | **16.8x** |
| 单表 `WHERE age > 90` | 45 | 0.111 ms | 0.153 ms | 0.7x |
### 怎么解读
- **前两行的差距来自谓词下推。** 条件筛掉了约 90% 的用户,配对次数从 25 万降到约 2.25 万,
时间直接掉一个数量级。筛得越狠,省得越多。
- **第二行只有投影下推。** 配对次数一点没变(没有条件可下推),只是每一行变窄了,
所以只有 1.3 倍 —— 收益温和但确实是正的。
- **第四行反而略慢。** 单表查询本来就没有下推空间(条件早已贴着扫描),
而优化器自己要走一遍规则,有几十微秒的固定开销(0.153 − 0.111 ≈ 0.04 ms)。
**优化不是零成本的**:查询越大、配对越多,这笔账才越划算。
---
## 加新用例
在 `src/Main.hs` 的 `queries` 列表里加一条 SQL 字符串就行,其余全自动
(会自动翻译、优化、跑两遍、比结果、算加速比)。
想测别的规模,直接命令行传参;想在文档里引用数字,记得把**数据规模一起写上** ——
脱离规模谈"快了几倍"是没有意义的。