はじめまして!
2013年度新卒入社で現在、スクールファンファーレのサーバサイドエンジニアを担当しているsuguruです。
スクファンの事前登録はコチラ!
JavaScriptの非同期処理を行うときに、Async.jsというとても便利なライブラリがあります。
しかしAsync.jsはパフォーマンスの面ではあまりチューニングされていなく、改善する余地があります。
そこでパフォーマンスを改善したNeo-Async.jsについて紹介させていただきます。
Async.jsとはJavaScriptの非同期処理を直線的に書くことができるライブラリです。
直線的に書くことで可読性が高く、バグが起こりにくいコードを実現できます。
まずは処理速度について紹介します。以下のバージョンで測定します。
計測環境は以下の3つの環境です。
図1: waterfallの実行例
waterfallでは約2倍の速度が出ており、フロントサイドではパフォーマンスの改善が期待できます。
ツールの仕様は以下の通りです。
実行環境は以下の通りです。
node・iojsでもレスポンス改善が期待できます。
ツールの仕様は以下の通りです。
![iojs_waterfall](https://stat.ameba.jp/user_images/20150128/00/ca-1pixel/7f/6c/p/o0445045613201474443.png?caw=800)
図2: nodeの速度比較
図3: iojsの速度比較
task数が大きくなるにつれ速度差・速度比が大きくなってきます。 Neo-Asyncではtask数が増えても高パフォーマンスが期待できます。
Neo-Asyncではほとんどのfunctionでサポートしており、これは何かと便利な機能です。
Neo-Asyncはとてもコードは冗長ですが、高パフォーマンスを実現することができました。
これからもより快適なゲームが作れるように努めて行きたいと思います。
スクファンもよろしくお願いします!(`・ω・´)
2013年度新卒入社で現在、スクールファンファーレのサーバサイドエンジニアを担当しているsuguruです。
スクファンの事前登録はコチラ!
JavaScriptの非同期処理を行うときに、Async.jsというとても便利なライブラリがあります。
しかしAsync.jsはパフォーマンスの面ではあまりチューニングされていなく、改善する余地があります。
そこでパフォーマンスを改善したNeo-Async.jsについて紹介させていただきます。
Async.js
Neo-Async.jsに入る前に、Async.jsを紹介します。Async.jsとはJavaScriptの非同期処理を直線的に書くことができるライブラリです。
直線的に書くことで可読性が高く、バグが起こりにくいコードを実現できます。
hoge(function(err, result) {また並列処理や他にも便利なメソッドを多数揃えています。
fuga(result, function(err, result) {
piyo(result, function(err, result) {
console.log(result);
});
});
});
// ↓
async.waterfall([
function (next) {
hoge(next);
},
function (result, next) {
fuga(result, next);
},
function (result, next) {
piyo(result, next);
}
], function(err, result) {
console.log(result);
});
Neo-Async.js
Neo-Async.jsとはAsync.jsと完全に互換性があり、 より高速でより利便性が高いライブラリです。まずは処理速度について紹介します。以下のバージョンで測定します。
- Async v0.9.0
- Neo-Async v0.4.8
フロントエンドの速度比較
フロントエンドのベンチマーク計測にjsPerfを利用しました。 実行環境やプログラムにより速度が全く異なってくるので、ほんの一例です。計測環境は以下の3つの環境です。
- Chrome 40.0.2214
- FireFox 34.0
- Safari 8.0.2
結果
![jsperf_waterfall](https://stat.ameba.jp/user_images/20150128/00/ca-1pixel/b8/00/p/o0800058813201474444.png?caw=800)
function | Chrome | FireFox | Safari | url |
---|---|---|---|---|
waterfall | 2.18 | 2.20 | 2.36 | http://jsperf.com/async-waterfall/7 |
series | 1.50 | 1.31 | 1.10 | http://jsperf.com/async-series/8 |
parallel | 15.67 | 10.17 | 5.01 | http://jsperf.com/async-parallel/5 |
parallelLimit | 1.35 | 1.41 | 1.11 | http://jsperf.com/async-parallel-limit/2 |
waterfallでは約2倍の速度が出ており、フロントサイドではパフォーマンスの改善が期待できます。
サーバサイドの速度比較
サーバサイドのベンチマーク計測には簡易計測ツールを作って調べました。ツールの仕様は以下の通りです。
- n回試行
- 毎回順番がランダム
- 毎回gcを走らせる
- n回の平均速度[μs]を計測
demo.js
var comparator = require('func-comparator'); // 今回作った計測ツール
var _ = require('lodash');
var async = require('async');
var neo_async = require('neo-async');
var count = 10; // parallelのタスク数
var times = 1000; // 試行回数
var array = _.shuffle(_.times(count));
var tasks = _.map(array, function(n) {
return function(next) {
next(null, n);
};
});
// ここのfuncsを交互ではなく毎回ランダムで実行する
var funcs = {
'async': function(callback) {
async.parallel(tasks, callback);
},
'neo-async': function(callback) {
neo_async.parallel(tasks, callback);
}
};
comparator
.set(funcs)
.option({
async: true,
times: times
})
.start()
.result(console.log);
実行
task数10で1000回実行した平均速度を比較します。実行環境は以下の通りです。
- node v0.10.35
- iojs v1.0.2
$ node --expose_gc demo.js // gcを使わずに実行も可能
$ iojs --expose_gc demo.js
結果
数値はn回の平均速度の比(Async/Neo-Async)になります。function | node | iojs |
---|---|---|
waterfall | 3.47 | 12.05 |
series | 1.98 | 6.38 |
parallel | 2.94 | 8.94 |
paralellLimit | 2.88 | 6.13 |
node・iojsでもレスポンス改善が期待できます。
waterfallの速度比較
taskのサイズによっても速度が大きく変わってくるため、task数の変化による速度変化を調べます。ツールの仕様は以下の通りです。
- task数がlowerからinterval間隔でupperまで実行
- 毎回順番がランダム
- 毎回gcを走らせる
- n回の平均速度[μs]を計測
demo2.js
var statistic = require('func-comparator').statistic;
var _ = require('lodash');
var async = require('async');
var neo_async = require('neo-async');
// サンプリング回数
var times = 100;
var create = function(count) {
// countはtask数
var array = _.shuffle(_.times(count));
var tasks = _.map(array, function(n, i) {
if (i === 0) {
return function(next) {
next(null, n);
};
}
return function(total, next) {
next(null, total + n);
};
});
var funcs = {
'async': function(callback) {
async.waterfall(tasks, callback);
},
'neo-async': function(callback) {
neo_async.waterfall(tasks, callback);
}
};
return funcs;
};
statistic
.create(create)
.option({
async: true,
times: times,
count: {
lower: 10,
upper: 1000,
interval: 10
}
})
.start()
.result(console.log)
.csv('waterfall_' + _.now());
実行
task数10~1000、間隔は10刻みで、毎回の試行回数は100回です。 実行環境は以下の通りです。- node v0.10.35
- iojs v1.0.2
$ node --expose_gc demo2.js
$ iojs --expose_gc demo2.js
結果
処理結果は以下の図になります。x軸はtask数、y軸は平均処理時間[μs]です。![iojs_waterfall](https://stat.ameba.jp/user_images/20150128/00/ca-1pixel/7f/6c/p/o0445045613201474443.png?caw=800)
図2: nodeの速度比較
![node_waterfall](https://stat.ameba.jp/user_images/20150128/00/ca-1pixel/75/00/p/o0445044513201474442.png?caw=800)
task数が大きくなるにつれ速度差・速度比が大きくなってきます。 Neo-Asyncではtask数が増えても高パフォーマンスが期待できます。
利便性の向上
underscoreやLo-DashではObjectをArrayのforEachのように実行することが当たり前のようにサポートされていますが、 Asyncではサポートされていません。Neo-Asyncではほとんどのfunctionでサポートしており、これは何かと便利な機能です。
var object = {
HOGE: 'hoge',
FUGA: 'fuga',
PIYO: 'piyo'
};
async.each(Object.keys(object), function(key, done) {
var str = object[key];
/* 処理 */
console.log(str);
done();
}, callback);
neo_async.each(object, function(str, done) {
/* 処理 */
console.log(str);
done();
}, callback);
終わりに
Asyncの実装はとてもきれいで勉強になりますので、ぜひ読んでいただきたいです。Neo-Asyncはとてもコードは冗長ですが、高パフォーマンスを実現することができました。
これからもより快適なゲームが作れるように努めて行きたいと思います。
スクファンもよろしくお願いします!(`・ω・´)