Javascriptでカプセル化のコスト

Filed Under (article, level☆☆☆☆) by on 07-02-2008

どうも、ひろきのだいちです。

Javascriptでカプセル化を実現する!の続編みたいなものです。

この記事を投稿したところ、chokodogさんから次のような指摘を受けました。
(コメントありがとうございます><)

カプセル化した場合、コンストラクタ内でメソッドが定義されているので、new するたびfunctionが定義され、prototypeで定義するよりメモリを多くとられてしまうということはないのでしょうか?

たしかにそのとおりです。

今回はカプセル化を先のような方法で行うとどのようなコストがかかるのかということ探ってみました。

メモリを使うのでは?ってどういうこと?

chokodogさんの指摘の意味を簡単に説明したいと思います。

まずは以下のjavascriptのプログラムを見てください。

JAVASCRIPT:
  1. //オブジェクトのプロトタイププロパティを設定
  2. Object.prototype.objprop=100;
  3. //Classオブジェクトの作成
  4. var Class=function(){};
  5. //Classのインスタンスオブジェクトの作成
  6. var instance=new Class();
  7. //Classプロトタイププロパティを設定
  8. Class.prototype.classprop=200;
  9. //instanceの
  10. instance.insprop=300;
  11.  
  12.  
  13. log(instance.objprop);
  14. //-->100
  15. log(instance.classprop);
  16. //-->200
  17. log(instance.insprop);
  18. //-->300
  19. log(instance.hasOwnProperty("objprop"));
  20. //->false
  21. log(instance.hasOwnProperty("classprop"));
  22. //->false   
  23. log(instance.hasOwnProperty("insprop"));
  24. //->false
  25. /*
  26.  ここでinstanceのプロトタイプチェーンをたどって
  27.  みつかったプロパティをOwnプロパティとして設定
  28. */
  29. instance.objprop=instance.objprop;
  30. instance.classprop=instance.classprop;
  31.  
  32. log(instance.hasOwnProperty("objprop"));
  33. // -->true
  34. log(instance.hasOwnProperty("classprop"));
  35. // -->true
  36. log(instance.hasOwnProperty("insprop"));
  37. // -->true

JavaScriptは通常のクラスベースのオブジェクト指向とことなりプロトタイプベースという別のプログラムパラダイムを採用しています。
JavaScriptのオブジェクトはすべてハッシュのようなものになっていますが、これに少しばかり細工がしてあります。

それがプロトタイプチェーンというやつです。

オブジェクトは、自身のプロパティで見つからないようなハッシュ値を自分自身のコンストラクタのプロトタイプへと探しにいきます。

たとえば、上のプログラムの場合、
objpropというプロパティを探しにいったときに

・自分自身のプロパティではない
・自分のコンストラクタのプロトタイププロパティではない。
・チェーンの終端であるオブジェクトのプロトタイププロパティで発見

というような順番で探っています。

しかし、次のようにするとプロトタイプ経由のプロパティを自分自身のプロパティとして利用することができます。

JAVASCRIPT:
  1. /*
  2.  ここでinstanceのプロトタイプチェーンをたどって
  3.  みつかったプロパティをOwnプロパティとして設定
  4. */
  5. instance.objprop=instance.objprop;
  6. instance.classprop=instance.classprop;
  7.  
  8. log(instance.hasOwnProperty("objprop"));
  9. // -->true
  10. log(instance.hasOwnProperty("classprop"));
  11. // -->true
  12. log(instance.hasOwnProperty("insprop"));
  13. // -->true

さて、プロトタイプチェーンの仕組みを踏まえて
次のようなプログラムを見てください。

JAVASCRIPT:
  1. /*
  2.     プロトタイプとしてメソッドを実装
  3. */
  4. Object.prototype.test=function(){alert(”hello”)};
  5. var t1={val:2};
  6. var t2={val:1};
  7.  
  8. // ===で同一のリファレンスか確認
  9. console.log(t1.test===t2.test);//true
  10.  
  11. /*
  12.     instanceのown propertyとして実装
  13. */
  14. var Class=function(){
  15. this.test=function(){alert(”hello!”);}
  16. }
  17. var c1=new Class();
  18. var c2=new Class();
  19.  
  20. //こちらは別のリファレンスになってしまう。
  21. console.log(c1.test===c2.test);//false

このように、オブジェクトのメソッドを定義する際に、
オウンプロパティとして実装する場合では、

2つのインスタンスの定義した関数のそれぞれのさすリファレンスがことなることがわかります。

リファレンスとはC言語でいえばポインタ。
つまりコンピュータのメモリ上の格納場所が異なるということです。

なので、

カプセル化した場合、コンストラクタ内でメソッドが定義されているので、new するたびfunctionが定義され、prototypeで定義するよりメモリを多くとられてしまうということはないのでしょうか?

冒頭のこのようなコメントを頂戴するわけです。
このコメントに対して、僕はあろうことか鼻くそほじるように
「トレードオフじゃないですか~。サーセンwwフヒヒ」(嘘おおげさ紛らわしい)と答えてしまいました。

やっぱりどのくらいコストがかかるのか確かめてみようと思い、実験を行いました。

実験内容

二つのjavascriptプログラムの実行時間とメモリ消費量を調べる。

JAVASCRIPT:
  1. /*
  2.     アンダーバー記法のクラス
  3. */
  4. //人間を作るときの関数(コンストラクタ)
  5. var Human=function(name,year){
  6.     this._year=year;
  7.     this.name=name;
  8.     this._checkEto();
  9. };
  10. //人間のDNA(設計図)を決める
  11. Human.prototype={
  12.     name:"",_year:1983,_eto:"亥",
  13.     //干支を生まれ年から計算する
  14.     _checkEto:function(){
  15.         this._eto=["子","丑","寅",
  16.             "卯","辰","巳",
  17.             "午","未","申",
  18.             "酉","戌","亥"][(this._year-4)%12];
  19.     },
  20.     //年齢を決める。
  21.     setYear:function(year){
  22.         this._year=year;
  23.         this._checkEto();
  24.     },
  25.     getYear:function(){return this._year;},
  26.     getEto:function(){return this._eto;}
  27. };

JAVASCRIPT:
  1. /*
  2.     クロージャによるカプセル化クラス
  3. */
  4. var Human=(function(){
  5.   var constructor=function(name,year){
  6.     var inner={
  7.       name:name,
  8.       eto:"",
  9.       year:year,
  10.       /*
  11.         private
  12.       */
  13.       checkEto:function(){
  14.         inner.eto=["子","丑","寅",
  15.           "卯","辰","巳",
  16.           "午","未","申",
  17.           "酉","戌","亥"][(inner.year-4)%12];
  18.       }
  19.     };
  20.     inner.checkEto();
  21.  
  22.     this.setName=function(name){
  23.       inner.name=name;
  24.     
  25.     };
  26.     this.setYear=function(year){
  27.       inner.year=year;
  28.       inner.checkEto();
  29.     };
  30.     this.getName=function(){
  31.       return inner.name;
  32.     };
  33.     this.getYear=function(){
  34.       return inner.year;
  35.     };
  36.     this.getEto=function(){
  37.       return inner.eto;
  38.     };
  39.   }
  40.   
  41.   return constructor;
  42. })();

この2つのプログラムの生成にかかるコストをこのような
プログラムをそれぞれに行い調査します。

JAVASCRIPT:
  1. for(var i=0;i<10000;i++){
  2. var x=new Human("DAICHI"+i,i);
  3. }

実行時間

# time js none.js
real    0m0.006s

# time js cap.js
real    0m0.007s

# time js underbar.js
real    0m0.007s

# time js c_cap_10000.js 
real    0m1.741s

# time js c_underbar_10000.js
real    0m0.867s

jsインタプリタにはspidermonkeyを使います。

none.jsはただjsインタプリタを起動するのにかかった時間をはかるために何も書いていないjsファイルをtouchして作っただけです。

読み込みにはそれぞれ0.001secしかかかっていません。
実行時間は10000個のインスタンス生成におよそ0.874秒のコストがかかっています。

ということは、カプセルかによって

87.4マイクロ秒

のコストが1つのインスタンス生成あたりにかかっているようです。

メモリ消費量について

次はメモリの消費量です。
これは昨日作ったfleafeedを使ってみます。
C言語でメモリ使用量をカウントする!

timeコマンドのオプション使えやって思った人は正しいんですが、
せっかくなので使ってみたくなっちゃったんだもん><

#fleafeed js none.js
< fleafeed >
-----------fleafeed results------------

--USING HEAP    :   48574 bytes

#fleafeed js cap.js
-----------fleafeed results------------

--USING HEAP    :   59641 bytes

# fleafeed js underbar.js
-----------fleafeed results------------

--USING HEAP    :   56019 bytes

# fleafeed js c_cap_10000.js
-----------fleafeed results------------

--USING HEAP    :12925508 bytes

# fleafeed js c_underbar_10000.js
-----------fleafeed results------------

--USING HEAP    : 1731685 bytes

とこんな感じ。

jsの実装はほとんどHEAPしか使わないので、HEAP領域のみをピックアップしてみました。

まず、読み込みにかかるコストの比較ですが

アンダーライン記法:11067 bytes
クロージャ記法	    :7745	bytes

あれ?なんかしらんけどクロージャ記法のほうが読み込みは少ないっぽいです。不思議。

次に生成1つあたりのメモリコストの違いですが
これはでかいですね。

これを計算してみると1つあたりのメモリコストは

1,110 bytes

となっているようです。およそ1kのメモリが余分に使われてしまうわけです。
これは確かにコスト高な感じはします。

富豪プログラミングをしたいひとにはオススメということで
どうでしょうか(泣)

Post a comment