小贝恩克
一个简单、小而轻巧的基准库!
您可以在多个 JavaScript 运行时运行基准, Tinybench 完全基于 Web API, 并使用适当的时间
process.hrtime 或者说 performance.now。 。 。 .
- 基于环境的准确和准确的时间
- 统计分析延迟和通量值:标准偏差,出错幅度,相差,百分位等.
- 货币支持
Event财务报告和审定财务报表EventTarget兼容事件- 无依赖关系
如果你需要更小的图书馆 比如小池或小池,请考虑提交 RFC (RFC ) (法语)
安装
$ npm install -D tinybench
使用量
你可以通过即时设定基准 Bench 分类并添加基准任务.
import { Bench } from 'tinybench'
const bench = new Bench({ name: 'simple benchmark', time: 100 })
bench
.add('faster task', () => {
console.log('I am faster')
})
.add('slower task', async () => {
await new Promise(resolve => setTimeout(resolve, 1)) // we wait 1ms :)
console.log('I am slower')
})
await bench.run()
console.log(bench.name)
console.table(bench.table())
// Output:
// simple benchmark
// ┌─────────┬───────────────┬───────────────────┬───────────────────────┬────────────────────────┬────────────────────────┬─────────┐
// │ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
// ├─────────┼───────────────┼───────────────────┼───────────────────────┼────────────────────────┼────────────────────────┼─────────┤
// │ 0 │ 'faster task' │ '63768 ± 4.02%' │ '58954 ± 15255.00' │ '18562 ± 1.67%' │ '16962 ± 4849' │ 1569 │
// │ 1 │ 'slower task' │ '1542543 ± 7.14%' │ '1652502 ± 167851.00' │ '808 ± 19.65%' │ '605 ± 67' │ 65 │
// └─────────┴───────────────┴───────────────────┴───────────────────────┴────────────────────────┴────────────────────────┴─────────┘
这个 add 方法接受任务名称和任务函数,从而可以基准
这个! 这种方法返回引用到法官案例,所以有可能
用它来创造另一个任务,例如.
注意任务名称在某种情况下应该总是独一无二, 因为 Tinybench 存储基于任务
他们的名字在一个 Map。 。 。 .
还注意到 tinybench 默认不记录任何结果。 你可以提取相关数据
从 bench.tasks 或运行基准后的任何其他 API,并任意处理.
可在 实例 目录.
文档
财务问题
Bench
Task
TaskResult
Events
两者 Task 财务报告和审定财务报表 Bench 类扩展 EventTarget 对象。 因此,您可以使用通用 addEventListener 财务报告和审定财务报表 removeEventListener 方法。 。 .
BenchEvents
// runs on each benchmark task's cycle
bench.addEventListener('cycle', (evt) => {
const task = evt.task;
});
// runs when timer saturation is detected for a task's measured samples
bench.addEventListener('warning', (evt) => {
const task = evt.task;
const reason = evt.reason; // 'zero-dominated' | 'low-distinct' | 'zero-mad'
});
TaskEvents
// runs only on this benchmark task's cycle
task.addEventListener('cycle', (evt) => {
const task = evt.task;
});
BenchEvent
同步检测
Tinybench 自动检测任务函数是否同步
检查是否提供了函数 AsyncFunction 或者它返回一个
Promise,通过一次调用提供的函数.
您也可以明确设置 async 选项到 true 或者说 false 添加时
任务,从而避免被检测。 设定 async: false 只为了一个真正的
同步任务; 测量返回 a 函数的同步成本
Promise,通过 overriddenDuration 相反(见
任务计量) (中文(简体) ).
const bench = new Bench()
bench.add('asyncTask', async () => {}, { async: true })
bench.add('syncTask', () => {}, { async: false })
bench.add(
'syncTaskReturningPromiseAsAsync',
() => {
return Promise.resolve()
},
{ async: true }
)
await bench.run()
货币
- 何时
mode设置为null(默认),货币被禁用. - 何时
mode设置为“任务”,每个任务的迭代(调用任务函数)同时运行. - 何时
mode设置为“ 下” , 不同的任务同时运行 。 同时循环.
const bench = new Bench({
concurrency: 'task', // The concurrency mode to determine how tasks are run.
threshold: 10, // Maximum concurrent iterations within a task. Defaults to Infinity.
})
await bench.run()
与 concurrency: null 或者说 'bench',每个任务都会持续到两个时间
符合预算和最低重迭数。 与 concurrency: 'task', (中文(简体) ),
在达到正限时停止排期;
threshold 限制该任务内同时重复而不是同时重复
基准任务。 取消重复限制 iterations: 0 需求
限定值 threshold (例如, threshold: 10),而不是默认值
Infinity这些规则也适用于: warmupTime 财务报告和审定财务报表 warmupIterations
内 warmup()。 。 。 . Task.warmupSync() 总是使用顺序规则,不管
的货币.
转换任务结果 console.table()
您可以将基准结果转换为适合
console.table() 使用 bench.table() 方法.
const table = bench.table()
console.table(table)
您也可以通过向表格提供转换函数来自定义表格输出 table 方法.
import { Bench, type ConsoleTableConverter, formatNumber, mToNs, type Task } from 'tinybench'
/**
* The default converter function for console.table output.
* Modify it as needed to customize the table format.
*/
const defaultConverter: ConsoleTableConverter = (task: Task): Record<string, number | string> => {
const state = task.result.state
return {
'Task name': task.name,
...(state === 'aborted-with-statistics' || state === 'completed'
? {
'Latency avg (ns)': `${formatNumber(mToNs(task.result.latency.mean))} \xb1 ${formatNumber(task.result.latency.rme)}%`,
'Latency med (ns)': `${formatNumber(mToNs(task.result.latency.p50))} \xb1 ${formatNumber(mToNs(task.result.latency.mad))}`,
'Throughput avg (ops/s)': `${Math.round(task.result.throughput.mean).toString()} \xb1 ${formatNumber(task.result.throughput.rme)}%`,
'Throughput med (ops/s)': `${Math.round(task.result.throughput.p50).toString()} \xb1 ${Math.round(task.result.throughput.mad).toString()}`,
Samples: task.result.latency.samplesCount,
}
: state !== 'errored'
? {
'Latency avg (ns)': 'N/A',
'Latency med (ns)': 'N/A',
'Throughput avg (ops/s)': 'N/A',
'Throughput med (ops/s)': 'N/A',
Samples: 'N/A',
Remarks: state,
}
: {
Error: task.result.error.message,
Stack: task.result.error.stack ?? 'N/A',
}),
...(state === 'aborted-with-statistics' && {
Remarks: state,
}),
}
}
const bench = new Bench({ name: 'custom table benchmark', time: 100 })
// add tasks...
console.table(bench.table(defaultConverter))
保留样本
默认情况下, Tinybench 不保存样本 latency 财务报告和审定财务报表 throughput 改为
最小化内存使用。 需要原始样本时启用保留样本
绘图、自定义分析或输出结果.
通过设置
retainSamples 选项到 true 创建时 Bench 实例:
const bench = new Bench({ retainSamples: true })
通过设置 retainSamples 选项到
true 添加任务时 :
bench.add(
'task with samples',
() => {
// Task logic here
},
{ retainSamples: true }
)
时间戳提供商
Tinybench可以利用不同的时间戳提供商来测量时间间隔.
默认使用 performance.now()。 。 。 .
这个 timestampProvider 选项可以在创建 Bench 举个例子 这个
接受一个 TimestampProvider 普通对象或缩写
供应商 hrtimeNow 财务报告和审定财务报表 performanceNow。 。 。 .
如果您使用 bun 运行时间,您也可以使用 bunNanoseconds 缩写.
你可以设置 timestampProvider 改为 auto 让Tinybench选择最多
根据运行时间提供精确可用的时间戳 .
import { Bench } from 'tinybench'
const bench = new Bench({
timestampProvider: 'hrtimeNow', // or 'performanceNow', 'bunNanoseconds', 'auto'
})
如果您想要提供自定义的时间戳提供者, 您可以创建执行对象
页:1 TimestampProvider 接口 :
import { Bench, type TimestampProvider } from 'tinybench'
// Custom timestamp provider using Date.now()
const dateNowTimestampProvider: TimestampProvider = {
name: 'dateNow', // name of the provider
fn: Date.now, // function that returns the current timestamp
toMs: ts => Number(ts), // convert the timestamp to milliseconds
fromMs: ts => ts, // convert milliseconds to the format used by fn()
}
const bench = new Bench({
timestampProvider: dateNowTimestampProvider,
})
您也可以设置 now 选项到返回当前时间戳的函数.
将改为 TimestampProvider 内部.
import { Bench } from 'tinybench'
const bench = new Bench({
now: Date.now,
})
超头修正
每个计时器呼叫( E)performance.now(), (中文(简体) ), process.hrtime.bigint()拥有一个
非零呼叫费用 C。用于一个任务,其真正持续时间 X 可比较
改为 C,原始测量样本 X + C 以计时器为主
而不是任务.
何时 subtractTimerOverhead: true 设置, 一个估计数 Ĉ 计算
施工时间一次通过 calibrateTimerOverhead, (中文(简体) ),
财务报告和审定财务报表 Math.max(0, raw_sample - Ĉ) 用作每个非重叠样本
在计算统计数据之前.
const bench = new Bench({ subtractTimerOverhead: true })
console.log(bench.timerOverhead) // calibrated Ĉ in ms (or undefined)
校正辅助器也出口给直接使用,其中
可配置的估算战略'median' 默认,或 'min' 编号 : 'p05':
import { calibrateTimerOverhead, hrtimeNowTimestampProvider } from 'tinybench'
const overhead = calibrateTimerOverhead(hrtimeNowTimestampProvider, {
estimator: 'p05',
pairs: 1024,
warmupPairs: 64,
})
出出洞来.
- 与
concurrency: 'task'- 管理费用经过校正 按顺序排列,不反映同时执行费用. 建筑业(和run()如果两者都设定了,则投掷. - 用于次级项目测量
X ≈ Ĉ(b) 与《京都议定书》max(0, …)夹子 截取下尾和偏差统计; 偏好overriddenDuration。 。 。 . - 当计时器过于粗糙,无法解决呼叫费用时——不到一半
校正对对产生正三角洲(呼叫成本)
C < R / 2, (中文(简体) ), 例如,aDate.now- 级计时器>= 1 ms——校正 返回时0选项变成禁用. - 每个样本都包含一个单一的任务调用,而不是一个分批循环,所以
每样计时器地板
C未摊销。 这是蓄意取舍: 它产生真实的每个样本分布(百分比、MAD、饱和度) 以较高分公司为代价C噪音地板。 用于以下工作 计时器谷物, 使用overriddenDuration。 。 。 . Ĉ不覆盖同步任务的await微任务转弯——即: 在所测量的窗口内,但没有校正对——所以 亚微秒样本即使与subtractTimerOverhead。 。 。 . 使用overriddenDuration用于这种分解工作.
任务计量
每一样本持续时间(overriddenDuration(中文(简体) )
任务函数可能返回包含 overriddenDuration
(以毫秒计). 该值被记录在计时器测量样本的外:
定时器仍然将任务函数置于括号内,但其测量是
已丢弃,未对替换的间接费用校正
数值。 用于外部定时工作或次级管理项目测量
计时器无法解决.
import type { FnReturnedObject } from 'tinybench'
bench.add('externally-timed', (): FnReturnedObject => {
const start = process.hrtime.bigint()
doWork()
const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6
return { overriddenDuration: elapsedMs }
})
过时的样本不包括在 Task.detectedResolution 财务报告和审定财务报表
从计时器饱和度检测.
重置费用(overriddenIterationCost(中文(简体) )
当一次重复数次内调时,每调值值你
想要在统计中与重复的实际成本不同
页:1 time 编号 : warmupTime 预算。 返回时 overriddenIterationCost
(ms) 宣布整个迭代的壁时成本:
import type { FnReturnedObject } from 'tinybench'
bench.add('batched', () => {
const innerCalls = 100
const start = process.hrtime.bigint()
for (let i = 0; i < innerCalls; i++) parse(input)
const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6
return {
overriddenDuration: elapsedMs / innerCalls, // mean per call in this batch
overriddenIterationCost: elapsedMs, // budget cost of the iteration
} satisfies FnReturnedObject
})
每个样本是每批呼叫的平均持续时间。 百分比和百分比 分散描述这些批次手段,而不是批次中个别的呼叫.
语义:
- 样本使用有效的
overriddenDuration,否则计时器测量 持续时间。 顺序预算使用有效的overriddenIterationCost, (中文(简体) ), 否则,在定时器 -- -- 超头修正之前,样本。 仅返回overriddenDuration因此,保持历史行为. - 任务当前
run()财务报告和审定财务报表warmup()使用真实的时钟进行预算 不检查overriddenIterationCost(既无存在也无价值). 每项任务的费用concurrency: 'bench'内Task.warmupSync()无论币种如何. - 两个字段的验证方法相同(无限数字为 ^ 0,
-0包含);无效值被视为不存在. - 成本领域必须报告为
in运算符(拥有或拥有) 页:1 缺出密钥的代理默认不是宣布的成本 。 错误 在检查或读取此字段时,则被视为一笔没有的费用. - 费用不影响定时器-管理头修正或定时器诊断;
它们在样本上运作.
result.totalTime财务报告和审定财务报表period仍为 样本来源。 仅返回成本 留下的样本计时器, 因此,计时器饱和度警告仍然会发生. - 申报的费用没有根据实际情况加以核实:
墙上小时成本是
C但宣布为O运行到大约time × C / O何时O < C,并缩短当O > C备注 时间预算支配着最小重迭数. - 警告,我马上回来. 重复费用
0(或-0无法满足正数 相接的时间预算;微小的正成本可能使达到不切实际. 与time: 0,运行可以在最小重迭数完成。 一样 规则适用于热身。 从外部定时器取消需要生成 切换到事件循环,不能中断runSync(); 见 执行期间中止。 。 。 .
定时诊断
之后 bench.run() (或 runSync()),每个任务都会暴露
detectedResolution:至少发生最小的阳性耐久性样本
两次,如果没有重复,则为最小的正样本。 只有计时器
考虑在任何高层修正之后的样本;如果无正面的,
结果 undefined。这是基于样本的热力学,不是保证
绑定在计时器的分辨率.
const task = bench.getTask('foo')
console.log(task?.detectedResolution) // e.g. 0.000041 (≈ 41 ns)
至少有10个这样的样本,小箱发送a 'warning' 活动
任务和板凳,如果超过一半为零,则少于
max(3, min(10, ⌊n / 1000⌋)) 出现差异值,或 MAD 为 0, 与
n > 100。该事件带有匹配
TimerSaturationReason数字 :
bench.addEventListener('warning', evt => {
console.warn(`timer-saturated: ${evt.task?.name} — ${evt.reason}`)
})
同样的高压和估测器被曝光为独立的助手
自定义分析 : detectTimerSaturation, (中文(简体) ),
classifyTimerSaturation, (中文(简体) ),
财务报告和审定财务报表 estimateResolution。 。 。 .
中止基准
Tinybench 支持使用中止基准 AbortSignal 在法官一级和任务一级:
座椅级中止
将一个信号传递到 Bench 构造器 :
const controller = new AbortController()
const bench = new Bench({ signal: controller.signal })
bench
.add('task1', () => {
// This will be aborted
})
.add('task2', () => {
// This will also be aborted
})
// Abort all tasks
controller.abort()
await bench.run()
// Both tasks will be aborted
任务级别中止
通过向任务选项传递信号中止单个任务而不影响其他任务:
const controller = new AbortController()
const bench = new Bench()
bench
.add(
'abortable task',
() => {
// This task can be aborted independently
},
{ signal: controller.signal }
)
.add('normal task', () => {
// This task will continue normally
})
// Abort only the first task
controller.abort()
await bench.run()
// Only 'abortable task' will be aborted, 'normal task' continues
执行期间中止
你可以中止基准 当它们运行时:
const controller = new AbortController()
const bench = new Bench({ time: 10000 }) // Long-running benchmark
bench.add(
'long task',
async () => {
await new Promise(resolve => setTimeout(resolve, 100))
},
{ signal: controller.signal }
)
// Abort after 1 second
setTimeout(() => controller.abort(), 1000)
await bench.run()
// Task will stop after ~1 second instead of running for 10 seconds
计时器触发取消需要任务或钩子向
事件循环,例如等待 I/O。 等待一个已经解开的承诺
这还不够。 单位 runSync(),任务或同步钩可以调用
controller.abort() ,但外部计时器
不能中断运行.
中止事件
两者 Bench 财务报告和审定财务报表 Task 发射 abort 中止时的事件 :
const controller = new AbortController()
const bench = new Bench()
bench.add(
'task',
() => {
// Task function
},
{ signal: controller.signal }
)
const task = bench.getTask('task')
if (!task) {
throw new Error('Task not found')
}
// Listen for abort events
task.addEventListener('abort', () => {
console.log('Task aborted!')
})
bench.addEventListener('abort', () => {
console.log('Bench received abort event!')
})
controller.abort()
await bench.run()
说明: 已中止的任务 task.result.state 等于 'aborted' 或者说
'aborted-with-statistics'。已在进行的重复可能在此之前完成
运行返回.
前作艺术
作者数
| 穆罕默德·巴盖尔 |
|---|
贷项
| 乌兹洛帕克 | 波约 |
|---|
捐款
请随意为项目创造问题/讨论,然后是公关!
发起人
你的赞助能对我们继续开放源码的工作产生巨大影响!