Benchmark.js
A benchmarking library that supports high-resolution timers & returns statistically significant results.
https://benchmarkjs.com/
Tutorial Advent Calendar 9日目担当、エンジニアの吉野です。
この記事では「Robotic Crowd を支える技術」として、より高パフォーマンスな実装に役立つベンチマークの測定についてご紹介します。
今回はbenchmark.js というJavaScript のベンチマークが測定できるライブラリを使用して、複数の処理のパフォーマンスを比較する方法を紹介しようと思います。
※ ライブラリのインストールにnpm、またはyarn を使うので、別途Node.js をインストールしておいてください。
まずベンチマークテストの実行環境を構築していきます。
$ mkdir benchmark-test
$ cd benchmark-test
$ npm init -y
-yオプションは
init 時の質問をスキップするためのオプションです。
$ npm i benchmark
環境構築はこれで完了です。
今回検証するのは「文字列aを1000個繋げた結果を出力したいけど、文字列をそのまま連結する方法と、配列に1000個入れてからjoinする方法、どちらがより良い実装なのか判断できない。。」というケースです。わかりやすいですね。
まずはテスト対象となるファイルを準備します。ファイル名はtest.jsとしておきましょう。
$ touch test.js
test.js には以下のように記述します。
// ライブラリの読み込み
const Benchmark = require('benchmark')
// 比較したい関数①を定義
function fromString() {
let str = ''
for (let i = 0; i < 1000; i++) {
str += 'a'
}
return str
}
// 比較したい関数②を定義
function fromArray() {
let arr = []
for (let i = 0; i < 1000; i++) {
arr.push('a')
}
arr.join('')
return arr
}
// テストスイートインスタンスを作成
const suite = new Benchmark.Suite
// テストケースの追加、オプションの設定
suite.on('start', () => {
console.log('Test start!')
}).add('fromString', () => {
fromString()
}).add('fromArray', () => {
fromArray()
}).on('cycle', (event) => {
console.log(String(event.target))
}).on('complete', function () {
console.log(`Fastest is ${this.filter('fastest').map('name')}`)
}).run({ async: true })
重要なのは
// テストケースの追加、オプションの設定
suite.on('start', () => {
console.log('Test start!')
}).add('fromString', () => {
fromString()
}).add('fromArray', () => {
fromArray()
}).on('cycle', (event) => {
console.log(String(event.target))
}).on('complete', function () {
console.log(`Fastest is ${this.filter('fastest').map('name')}`)
}).run({ async: true })
この部分です。
suite.on は各イベント(start、cycle、completeなど)発生時に実行したい処理を指定します。
suite.add は比較したい関数をテストケースとして追加します。
Fastest is ${this.filter('fastest').map('name’)}
ここはわかりにくいですが、filter('fastest') はbenchmark.js の機能で、最も処理が高速だったテストケースを抽出し、.map('name') でそのテストケースの名前を表示してくれます。
これで準備は完了です。実際にベンチマークを計測してみましょう。
benchmark-test ディレクトリ直下で下記コマンドを叩くと、テストスイートが実行されます。
$ node test.js
しばらく待つと結果が表示されます。
Test start!
fromString x 136,236 ops/sec ±0.98% (80 runs sampled)
fromArray x 44,666 ops/sec ±1.46% (90 runs sampled)
Fastest is fromString
こんな結果が出力されました。何やら fromString の方が速そうということはわかりますが、それ以外のことがさっぱりなので、ライブラリの作者による解説を参考にします。
fromString x 136,236 ops/sec ±0.98% (80 runs sampled)
この文章は fromString 関数が1秒間に136,236回(operations/second)実行されたということを意味します。
±0.98%は誤差、80 runs sampled は安定した結果を出すまでに 80回テストしたことを意味します。
同様に、fromArray 関数は1秒間に44,666回実行されたことがわかります。
両者を比較した結果、fromString 関数の方が1回あたりの実行時間が短かったため、Fastest is fromString と出力されているわけですね。これでどちらの方針で実装すればいいか判断できました。
開発している中で複数の実装方法で悩むことは少なく無いと思います。
そんな時はコードの簡潔さや可読性だけでなく、パフォーマンスにも目を向けてみてはいかがでしょうか。
今回はJavaScript のベンチマーク測定を紹介しましたが、どの言語でも測定ツールはあるはずなので、これまで気にされていなかった方は是非一度調べてみてください!