性能基准测试:使用 hyperfine 或 benchmark.js 对比 Lodash
在开发一个工具函数库时,功能正确性只是基础,性能表现才是其能否在生产环境中替代 Lodash、Ramda 等成熟库的关键。
通过 性能基准测试(Benchmarking),我们可以:
- 量化函数执行速度
- 识别性能瓶颈
- 与 Lodash 等库进行公平对比
- 证明“轻量替代”的可行性
以下是使用 hyperfine(命令行)和 benchmark.js(JavaScript 内)进行基准测试的完整实践。
1. 为什么需要性能基准测试?
常见误区
- “我的函数更短,所以更快”
- “没有循环,所以性能更好”
- “V8 优化了,不需要测”
真实场景需求
- 高频调用函数(如
isString,pipe)的微小开销会累积成显著延迟。 - 在数据量大的场景(如
chunk,deepGet),算法复杂度直接影响用户体验。
对比目标:Lodash
Lodash 是事实标准,但包含大量未使用的功能。我们的目标是:
- 功能对等
- 体积更小
- 性能相当或更优
2. 工具选择:hyperfine vs benchmark.js
| 工具 | 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
hyperfine | 命令行工具 | ⚡ 启动快,进程隔离,结果稳定 | 需编译为可执行文件 | CLI 脚本、Node.js 入口 |
benchmark.js | JS 库 | 🧪 直接在代码中测试,可测内部函数 | 受 Node.js 启动时间影响 | 开发时快速验证、CI 集成 |
推荐组合:
- 开发时用
benchmark.js快速迭代 - 发布前用
hyperfine做最终对比
3. 使用 benchmark.js 进行 JavaScript 内测试
安装
bash
npm install benchmark lodash示例:对比 pipe 函数
ts
// benchmarks/pipe.benchmark.ts
import { Suite } from 'benchmark'
import _ from 'lodash'
import { pipe } from '../src/function/pipe'
// 测试函数
const add1 = (x: number) => x + 1
const mul2 = (x: number) => x * 2
const sub3 = (x: number) => x - 3
// 构造测试套件
const suite = new Suite('pipe performance')
// 测试自研 pipe
suite.add('custom pipe', () => {
const fn = pipe(add1, mul2, sub3)
fn(10)
})
// 测试 Lodash flow(等价于 pipe)
suite.add('lodash flow', () => {
const fn = _.flow(add1, mul2, sub3)
fn(10)
})
// 运行
suite
.on('cycle', (event: any) => {
console.log(String(event.target))
})
.on('complete', function (this: any) {
console.log('Fastest is ' + this.filter('fastest').map('name'))
})
.run({ async: false })输出示例
text
custom pipe x 5,234,567 ops/sec ±1.23% (89 runs sampled)
lodash flow x 4,876,123 ops/sec ±0.98% (92 runs sampled)
Fastest is custom pipe示例:对比 omit 函数
ts
// benchmarks/omit.benchmark.ts
import { Suite } from 'benchmark'
import _ from 'lodash'
import { omit } from '../src/object/omit'
const obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }
suite.add('custom omit', () => {
omit(obj, ['b', 'd'])
})
suite.add('lodash omit', () => {
_.omit(obj, ['b', 'd'])
})
// ... 运行逻辑同上4. 使用 hyperfine 进行系统级基准测试
hyperfine 通过多次运行命令并统计时间,提供更接近真实环境的性能数据。
安装 hyperfine
bash
# macOS
brew install hyperfine
# Ubuntu/Debian
sudo apt install hyperfine
# Cargo
cargo install hyperfine准备 CLI 测试脚本
ts
// cli/pipe-test.ts
import { pipe } from '../src/function/pipe'
const add1 = (x: number) => x + 1
const mul2 = (x: number) => x * 2
const sub3 = (x: number) => x - 3
// 高频调用模拟
for (let i = 0; i < 10000; i++) {
pipe(add1, mul2, sub3)(i)
}运行 hyperfine
text
# 编译并测试
hyperfine \
'node cli/pipe-test.js' \
'node cli/lodash-pipe-test.js'
# 输出
Benchmark 1: node cli/pipe-test.js
Time (mean ± σ): 85.2 ms ± 3.1 ms [User: 78.5 ms, System: 6.2 ms]
Range (min … max): 80.1 ms … 95.3 ms 34 runs
Benchmark 2: node cli/lodash-pipe-test.js
Time (mean ± σ): 102.4 ms ± 4.8 ms [User: 95.1 ms, System: 7.0 ms]
Range (min … max): 95.6 ms … 115.2 ms 29 runs
Summary
'node cli/pipe-test.js' was 1.20x faster than 'node cli/lodash-pipe-test.js'5. 优化建议:如何提升性能
通用优化策略
| 问题 | 优化方案 |
|---|---|
多次 Object.keys() | 缓存结果 |
频繁 array.slice() | 使用索引迭代 |
| 深层嵌套对象访问 | 避免 try/catch |
| 类型检查开销 | typeof 优于 toString.call()(若安全) |
示例:优化 deepGet
text
// 优化前:每次调用都 split
const deepGet = (obj, path) => {
const paths = path.split('.')
// ...
}
// 优化后:缓存 split 结果
const deepGet = (() => {
const cache = new Map<string, string[]>()
return (obj, path: string) => {
let paths = cache.get(path)
if (!paths) {
paths = path.split('.')
cache.set(path, paths)
}
// ...
}
})()6. CI/CD 集成:自动化性能监控
在 GitHub Actions 中集成基准测试:
yaml
# .GitHub/workflows/benchmark.yml
name: Benchmark
on: [push, pull_request]
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: npm run build
- run: npm install -g hyperfine
- run: hyperfine 'node dist/pipe-test.js' 'node dist/lodash-pipe-test.js'注意:CI 环境性能波动大,建议多次运行取平均值。
7. 性能报告:可视化对比
使用 benchmarks/report.js 生成 HTML 报告:
js
// 可结合 benchmark.js + chart.js 生成图表
// 或使用 hyperfine --export-json results.json结语:性能是工具库的“硬通货”
功能完整只是入场券,性能卓越才是脱颖而出的关键。
通过 benchmark.js 和 hyperfine,我们不仅能证明:
- 我们的
pipe比 Lodashflow快 1.2x omit内存占用更少debounce响应更及时
更重要的是,我们建立了一套可量化、可重复、可验证的性能保障体系。
在微优化的时代,每一微秒的节省,都是对开发者体验的尊重。
当你能自信地说:“我们的轻量库在性能上不输 Lodash”,你才真正拥有了替代它的资格。