JavaScriptの簡易プロファイラ

JavaScriptで少し大きなアプリケーションを作っていると、処理速度がどんどん遅くなってしまうことがある。処理速度を向上させるには、ボトルネックとなっている処理を見つけて、その部分のパフォーマンスを改善するのが一般的である。そこで、ボトルネックを見つけるのに便利なプロファイラを作ってみた。

コードはこちら。

var Profiler = Class.create();
Profiler.prototype.extend({
    initialize: function(){
	this.keys = [];
	this.records = {};
    },
    observe: function(object, name){
	name = name || "Method";
	for (property in object) {
	    if(typeof object[property] == "function"){
		var key = name + "::" + property;
		this.keys.push(key);
		this.records[key] = [];
		object[property] = function(__method, __timeRecord){
		    return function(){
			var start = new Date;
			var rv =  __method.apply(this, arguments);
			__timeRecord.push( (new Date) - start);
			return rv;
		    }
		}(object[property], this.records[key])
	    }
	}
    },
    result: function(){
	var out = []
	for(var i = 0; i < this.keys.length; i++){
	    var rs = this.records[this.keys[i]];
	    out.push( this.keys[i] + 
		     " count:" + rs.length + 
		     " total:" + rs.sum() + 
		     " mean:" + Math.floor(rs.mean() * 10) / 10 +
		     " max:" + Math.floor(rs.max() * 10) / 10 +
		     " min:" + Math.floor(rs.min() * 10) / 10 );
        }
	return out;
    },
    alert: function(){
	alert(this.result().join("\n"));
    }
})

なお、prototype.jsを利用している。また、Arrayクラスに次のメソッドを追加している。

Array.prototype.extend({
  sum: function(){
      for(var i = 0, sum = 0; i < this.length; i++){
	  sum += this[i];
      }
      return sum;
  },
  mean: function(){
      return (this.length > 0) ? (this.sum() / this.length) : 0;
  },
  max: function(){
      if(this.length == 0){
	  return 0;
      }
      for(var i = 1, max = this[0]; i < this.length; i++){
	  if(this[i] > max){
	      max = this[i];
	  }
      }
      return max;
  },
  min: function(){
      if(this.length == 0){
	  return 0;
      }
      for(var i = 1, min = this[0]; i < this.length; i++){
	  if(this[i] < min){
	      min = this[i];
	  }
      }
      return min;
  }
})

使い方は、まず、Profilerクラスのインスタンスを生成した後、observeメソッドで、メソッドの実行時間を監視したいオブジェクトを指定する。なお、このとき普通は、クラスのprototypeを指定することになる。例えば、次のように指定する。

p = new Profiler;
p.observe(Worker.prototype, "Worker");

すると、Workerクラスの全てのメソッド呼び出しにかかった実行時間が記録される。記録した結果はresultメソッド、alertメソッドから呼び出せるので、適宜加工すれば良い。例えば、次のような結果が得られる。

Worker::initialize count:2 total:0 mean:0 max:0 min:0
Worker::run count:19 total:63 mean:3.3 max:16 min:0
Worker::extend count:0 total:0 mean:0 max:0 min:0
Worker::start count:19 total:0 mean:0 max:0 min:0

なお、複数のクラスを監視するには、複数回observeを呼び出せば良い。
また、ブラウザ環境で、クラスを使わずトップレベルに記述した関数を調べるには、

p.observe(window)

とすれば良い。