はじめに
Tinybenchは、JavaScriptコードのパフォーマンス計測を簡単に行える軽量ベンチマーキングライブラリですね。
GitHubで2,200スター以上を獲得していて、Vitestのベンチマーク機能にも採用されている実績があります。個人的には「ちょっとコードの速度を比較したい」というシーンで重宝しています。
正直なところ、以前はconsole.timeで雑に計測してたんですよ。でもそれだと統計的な信頼性がない。かといってbenchmark.jsは設定が面倒だし、最近メンテナンスされてない。そんな時にTinybenchを見つけて「これだ」となりました。
ライセンスはMITで、安心して使えます。
Tinybenchとは
Tinybenchは「tinylibs」プロジェクトが提供する、シンプルで高精度なベンチマーキングライブラリです。
特筆すべきは、ミニファイ + gzip後でわずか2KB程度というサイズ感。それでいて、標準偏差やパーセンタイルなどの統計情報まで自動で計算してくれる。機能と軽量さのバランスが良い。
特徴・メリット
1. 軽量で依存関係なし
2KB程度という軽さで、外部ライブラリへの依存もゼロ。プロジェクトに追加しても負担にならない。
これ、意外と重要で、ベンチマーク用ライブラリが重いと本末転倒なんですよね。
2. 高精度なタイミング測定
環境に応じてprocess.hrtime(Node.js)またはperformance.now()(ブラウザ)を使い分けます。ナノ秒レベルの精度で計測できるので、細かい最適化の検証にも使える。
3. 統計分析が自動
単純な平均だけじゃなく、以下の統計情報を自動で算出してくれます:
- 平均実行時間
- 標準偏差
- 誤差範囲(MOE: Margin of Error)
- 分散
- パーセンタイル(p75, p99など)
- 1秒あたりの実行回数(ops/sec)
30代になって思うのは、「なんとなく速そう」じゃなくて数値で比較できるのは大事だということ。
4. 非同期処理に対応
async/awaitパターンをネイティブでサポートしています。APIコールやDBクエリなど、非同期処理のベンチマークも簡単に取れる。
5. イベントシステム
EventTarget互換のイベントシステムを搭載。タスクの開始・終了時にフックを仕込める。CI/CDでの利用や、計測ログの保存に便利です。
インストール方法
前提条件
Node.js 14以上を推奨。ブラウザでも動きます。
インストール
# npm
npm install -D tinybench
# pnpm
pnpm add -D tinybench
# yarn
yarn add -D tinybench
# bun
bun add -D tinybench
開発用の依存として追加するのが一般的ですね。
基本的な使い方
シンプルなベンチマーク
import { Bench } from 'tinybench'
const bench = new Bench({ time: 100 })
bench
.add('Array.push', () => {
const arr = []
for (let i = 0; i < 1000; i++) {
arr.push(i)
}
})
.add('Array spread', () => {
let arr = []
for (let i = 0; i < 1000; i++) {
arr = [...arr, i]
}
})
await bench.run()
console.table(bench.table())
これで2つの処理のパフォーマンス比較ができます。bench.table()で整形されたテーブルが出力されるので、結果が見やすい。
非同期処理のベンチマーク
import { Bench } from 'tinybench'
const bench = new Bench({ time: 1000 })
bench
.add('fetch sequential', async () => {
await fetch('https://api.example.com/data1')
await fetch('https://api.example.com/data2')
})
.add('fetch parallel', async () => {
await Promise.all([
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2')
])
})
await bench.run()
console.table(bench.table())
非同期処理の最適化を検証する時に使えます。
詳細な統計情報の取得
import { Bench } from 'tinybench'
const bench = new Bench({ time: 500 })
bench.add('target function', () => {
// 計測したい処理
JSON.parse(JSON.stringify({ foo: 'bar', num: 123 }))
})
await bench.run()
// 各タスクの詳細結果
for (const task of bench.tasks) {
const result = task.result
console.log(`Task: ${task.name}`)
console.log(` Mean: ${result.mean.toFixed(4)}ms`)
console.log(` Std Dev: ${result.sd.toFixed(4)}ms`)
console.log(` Ops/sec: ${result.hz.toFixed(2)}`)
console.log(` Samples: ${result.samples.length}`)
console.log(` P75: ${result.p75.toFixed(4)}ms`)
console.log(` P99: ${result.p99.toFixed(4)}ms`)
}
オプション設定
const bench = new Bench({
name: 'My Benchmark',
time: 1000, // 各タスクの実行時間(ms)
iterations: 100, // 最小イテレーション回数
warmup: true, // ウォームアップ実行
warmupTime: 100, // ウォームアップ時間(ms)
warmupIterations: 5 // ウォームアップのイテレーション
})
warmupオプションは地味に大事で、JITコンパイラの最適化が効く前の計測を除外できます。より正確な結果が欲しい時はオンにしておくのがおすすめ。
実践的なユースケース
アルゴリズムの比較
ソートアルゴリズムの比較例:
import { Bench } from 'tinybench'
const bench = new Bench({ time: 500 })
const testData = Array.from({ length: 1000 }, () => Math.random())
bench
.add('Array.sort', () => {
const arr = [...testData]
arr.sort((a, b) => a - b)
})
.add('Quick sort', () => {
const arr = [...testData]
quickSort(arr)
})
await bench.run()
console.table(bench.table())
「理論上はこっちが速いはず」を実際に検証できる。これ、意外と結果が予想と違うことがあるんですよね。
ライブラリの選定
似たような機能を持つライブラリを比較する時にも使えます。
import { Bench } from 'tinybench'
import dayjs from 'dayjs'
import { format } from 'date-fns'
const bench = new Bench({ time: 500 })
const testDate = new Date()
bench
.add('dayjs', () => {
dayjs(testDate).format('YYYY-MM-DD')
})
.add('date-fns', () => {
format(testDate, 'yyyy-MM-dd')
})
.add('native', () => {
testDate.toISOString().split('T')[0]
})
await bench.run()
console.table(bench.table())
コスパ的に、ライブラリ選定時にパフォーマンスも判断材料に入れられるのは良い。
CI/CDでの回帰検知
イベントシステムを使って、パフォーマンスの閾値チェックができます:
import { Bench } from 'tinybench'
const bench = new Bench({ time: 1000 })
const THRESHOLD_MS = 10 // 10ms以上かかったらエラー
bench.add('critical function', () => {
// パフォーマンスが重要な処理
})
await bench.run()
const result = bench.tasks[0].result
if (result.mean > THRESHOLD_MS) {
console.error(`Performance regression detected: ${result.mean.toFixed(2)}ms > ${THRESHOLD_MS}ms`)
process.exit(1)
}
console.log(`Performance OK: ${result.mean.toFixed(2)}ms`)
GitHub Actionsに組み込めば、パフォーマンス劣化を自動で検知できます。
Vitestとの連携
Vitestのベンチマーク機能は内部でTinybenchを使っています。
// bench.test.ts
import { bench, describe } from 'vitest'
describe('string operations', () => {
bench('concat with +', () => {
const str = 'hello' + ' ' + 'world'
})
bench('concat with template', () => {
const str = `hello world`
})
})
vitest bench
テストと同じワークフローでベンチマークも実行できるのは便利ですね。
benchmark.jsとの比較
正直な比較をしておきます。
| 観点 | Tinybench | benchmark.js |
|---|---|---|
| サイズ | 2KB | 30KB+ |
| メンテナンス | 活発 | 停滞気味 |
| TypeScript | ネイティブ対応 | 型定義が古い |
| 非同期対応 | ネイティブ | 工夫が必要 |
| API | シンプル | やや複雑 |
| 統計情報 | 充実 | 充実 |
benchmark.jsは長年使われてきた定番ですが、最近はメンテナンスが滞っています。新規プロジェクトならTinybench一択ですね。
まとめ
Tinybenchを使い始めて感じた変化:
- 計測の手軽さ: console.timeから卒業できた
- 信頼性: 統計的に意味のある結果が得られる
- 可読性: テーブル出力で結果が見やすい
- 軽量さ: プロジェクトへの影響が最小限
- 保守性: TypeScript対応でIDEの補完が効く
「なんとなく速そう」から「数値で証明できる」に変わるのは大きい。
パフォーマンス最適化って、計測なしにやると的外れな改善に時間を使いがち。Tinybenchがあれば、まず計測して、改善して、また計測する。このサイクルを回すのが楽になります。
ライブラリ選定やアルゴリズム比較はもちろん、普段の開発でも「これ、どっちが速いんだろう」と思った時にサッと使えるのがQOL上がりますね。