2014年7月4日金曜日

Snap.svg v0.3.0のメモリリークを回避するパッチ

Snap.svgの使い方まとめでも書いておいたけれど,Snap.svgはオブジェクトのキャッシュを開放する術がありません.従ってオブジェクトの生成・廃棄を繰り返すことでメモリリークを引き起こします.

この問題は公式に認識されており,v0.3.0で解決したとあるのですが,コードを読む限り正しく実装されていません.

このことについての質問があったため,とりあえず回避コード(パッチ)を作ってみました.なお,詳しい動作検証は行っていませんので,導入は自己責任でお願いいたします.

※ライセンスはSnap.svgと同等としますので,ご自由にお使いください.



問題の本質


SnapメソッドやSnap.elメソッド等で得たElementオブジェクトは変数「hub」において連想配列形式(key-value)としてキャッシュされています.

この仕組みにより,Element.data,Element.click等の整合性が保たれるのですが,Elementオブジェクトをhubキャッシュから削除する仕組みが備わっていません.

v0.3.0ではこの削除処理をタイマーを用いて実装していますが,次の点に於いて更に根深い問題を追加してしまいました.
  • DOMにアタッチされていないSVGElementに対応するElementオブジェクトを削除しているため,処理の都合上一時的にDOMから切り離したElementオブジェクトがタイミングによっては無効化されてしまう.
  • 再帰的な処理を行っていないため,トップレベルのSVG要素(Paperオブジェクト)が廃棄されても,その子Elementオブジェクトが開放されるとは限らない(未検証)

キャッシュの開放機構を追加する


そこで筆者はv0.3.0でのコードをあえてコメントアウトし,新たにSnap.releaseメソッドを追加しました.
  1. // Hub garbage collector every 10sでソースコードを検索し,処理をコメントアウトする.
  2. 下記のコードを追加する.

// comment out by DEFGHI1977 2014.07.04
// Hub garbage collector every 10s
/*setInterval(function () {
    for (var key in hub) if (hub[has](key)) {
        var el = hub[key],
            node = el.node;
        if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) {
            delete hub[key];
        }
    }
}, 1e4);*/
Snap.release = function(node){
 var key = node.snap;
 if(key){
  var el = hub[key];
  if(el){
   delete el.node;
   delete hub[key];  
  }
  delete node.snap;
  var children = node.childNodes;
  for(var i = 0, len = children.length; i<len; i++){
   Snap.release(children[i]);
  }
 }else if(node.node){
  Snap.release(node.node); 
 }
};
 
NOTE:要するに変数hubがElementオブジェクトへの強参照を持っているため, オブジェクトの開放が上手く行っていないのです. つまりECMAScript6で導入されるWeakMapを使って既存のロジックを弱参照に書き換える方法もあります. (この場合WeakMapをサポートしないIE9/10の扱いが問題となります.)

使い方

キャッシュから開放したいオブジェクトをSnap.releaseメソッドに渡します.
使用例)

var paper = Snap(200,200);
var circle = paper.circle(100,100,80);
//circleオブジェクトをDOMから切り離し,キャッシュから開放する.
Snap.release(circle.remove());

Snap.releaseメソッドはSVGDOMオブジェクトとSnap.svgオブジェクト間の関連付けを再帰的に開放し,関連するElement(Paper)オブジェクトをキャッシュから削除します.

キャッシュの開放責任をプログラマ側に委ねる事になりますが,余計な処理を行なっていない分制御が容易になります.

補足

Snap.svgでメモリリークを引き起こすのはたしかに問題ですが,メモリリークを引き起こしうるコードはそもそもDOMオブジェクトの生成・廃棄を行い過ぎとも言えます.SVGDOMを用いたプログラムを組む場合は,出来る限りDOMオブジェクトの生成を少なくするように工夫する事が大切です. 

0 件のコメント:

コメントを投稿