フロントエンド界隈では、ES6やTypeScriptなどが積極的に採用されるようになり、Underscore.jsなどのユーティリティライブラリはオワコンだと言われて久しいですが1、果たしてそうでしょうか(疑問)。
配列や連想配列から特定のデータだけを取得する処理にfilterがあります。RubyでいうところのEnumerable#select、Enumerable#find_allに相当するメソッドですね。JavaScriptの各種ユーティリティライブラリでもこのフィルタ処理に該当する関数がいくつか定義されたものがあり、今回は、lodash(underscore.jsからインスパイアされた有名ライブラリ)、ES6ネイティブに実装されたArray#filter関数、Ramda(関数型プログラミングにインスパイアされた新進気鋭のライブラリ)の各種filter関数の性能の比較をしてみます。
ベンチマーク用の処理は以下の通りです。
- 0~10000までの乱数をN個含んだ配列を生成しそのうちの偶数を取り出す。
- 1steo=1000000刻みで50000000になるまで、各処理を実行し、処理時間(ms)を計測する。
下記がベンチマーク用のコードです。lodashには乱数生成用の関数_.randomが定義されているのでそれを用います。
const _ = require("lodash"); const R = require("ramda"); function a(n){ console.time('loadash-filter' + n.toString()); let min = 0; let max = 10000; x = _.times(n, ()=> _.random(min, max)).filter((i)=>(i%2)==0); console.timeEnd('loadash-filter' + n.toString()); return x; } function c(n){ console.time('ramda-filter' + n.toString()); let max = 10000; let targets = []; for(var i=0;i<n;++i) targets.push(Math.floor(Math.random() * Math.floor(max))); x = R.filter((i)=>(i%2)==0, targets); console.timeEnd('ramda-filter' + n.toString()); return x; } function b(n){ console.time('native-filter' + n.toString()); let max = 10000; let targets = []; for(var i=0;i<n;++i) targets.push(Math.floor(Math.random() * Math.floor(max))); x = targets.filter((i)=>(i%2)==0); console.timeEnd('native-filter' + n.toString()); return x; } function run(i){ a(i); b(i); c(i); } const max = 50000000; _(_.range(0, max, 1000000)).each((i)=> run(i));
これを実行し、いい感じにグラフ化したものが以下です。
ES6のfilter関数とRamdaのfilter関数はArray中のデータ量が大きくなっても実行速度が大きくて変わらないのに対して、lodashはstep35付近(N=30000000付近)で実行性能が大きく落ちる(3倍近く)ことがわかります
今回確認に用いたライブラリは以下です。
株式会社あしたのチームではフロントエンドにも強いエンジニアを募集しています。