2016 02 04

XMLの3

昨日の続き。

メモリを食っているのがHTMLのDOM構築らしいので、これをXMLのDOMと並存しなくても済むように変更してみるのはどうだろう。

具体的には、XMLファイルのDOMからデータを取り出した都度HTMLのDOMを作るのではなく、innerHTMLとして使える文字列に変換して貯めておき、最後に一括してHTML変換する。 文字列や文字列の配列はDOMほどメモリを消費しないだろうし、HTMLへの変換のタイミングではXMLのDOMの要素は読み終わっているのでGCに回すこともできそう。 XMLのDOMがそうメモリを食っているわけじゃないので改善効果はあんまり期待できないが、無いよりはマシではないか。

ということで、やってみた。

function doLoad() { try { document.getElementById( "base" ).innerHTML = domToHtml( loadXml( getFilename() ), [] ).join( "" ); } catch ( e ) { alert( e ); } function getFilename() { var filename = document.getElementById( "txtFile" ).value; if ( !filename ) { throw "ERROR : filename"; } return filename; } function loadXml( filename ) { var dom = new ActiveXObject( "MSXML2.DOMDocument" ); if ( !dom.load( filename ) ) { throw "ERROR : xml load"; } return dom; } function domToHtml( node, htmlAry ) { switch ( node.nodeType ) { case 1: // element elementToHtml( node, htmlAry ); break; case 3: // text textToHtml( node, htmlAry ); break; case 4: // cdata cDataToHtml( node, htmlAry ); break; case 7: // processing instruction processingInstructionToHtml( node, htmlAry ); break; case 9: // document documentToHtml( node, htmlAry ); break; case 10: // doctype doctypeToHtml( node, htmlAry ); break; case 5: // entity reference case 6: // entity case 8: // comment case 11: // document fragment case 12: // notation break; default: throw "ERROR : unsupported nodeType : " + node.nodeType; } return htmlAry; } function elementToHtml( node, htmlAry ) { var attributes = ""; if ( node.attributes ) { attributes = attributesToString( node.attributes ); } if ( node.hasChildNodes() ) { htmlAry.push( "<div>&lt;" + node.nodeName + attributes + "&gt;" ); Array.prototype.forEach.call( node.childNodes, function ( childNode ) { domToHtml( childNode, htmlAry ); } ); htmlAry.push( "&lt;/" + node.nodeName + "&gt;</div>" ); } else { htmlAry.push( "<div>&lt;" + node.nodeName + attributes + " /&gt;</div>" ); } } function textToHtml( node, htmlAry ) { htmlAry.push( node.nodeValue ); } function cDataToHtml( node, htmlAry ) { htmlAry.push( "<div>&lt;![CDATA[<div>" + node.nodeValue + "</div>]]&gt;</div>" ); } function processingInstructionToHtml( node, htmlAry ) { htmlAry.push( "<div>&lt;?" + node.nodeName + " " + node.nodeValue + "?&gt;</div>" ); } function documentToHtml( node, htmlAry ) { if ( node.hasChildNodes() ) { Array.prototype.forEach.call( node.childNodes, function ( childNode ) { domToHtml( childNode, htmlAry ); } ); } } function doctypeToHtml( node, htmlAry ) { htmlAry.push( "<div>&lt;!DOCTYPE" + node.nodeValue + "&gt;</div>" ); } function attributesToString( attributes ) { return Array.prototype.reduce.call( attributes, function ( a, b ) { return a + ' ' + b.name + '="' + b.value + '"'; }, "" ); } }

昨日との違いは、domToHtmlとその下請け関数の第二引数が表示用データの配列になったことと、各下請け関数内の処理がdiv要素の生成ではなくdiv文字列の生成になったこと。

そして動作確認。

小さいファイルは問題無い。 って、そりゃ当然か。

大きなファイルは、相変わらず このスクリプトの実行を中止しますか? の警告が出るが、処理を続行した時の完遂率はちょっと高くなった気がする。 あと、こっちの方が速い。

タスクマネージャーでメモリの状況を見ると、XMLファイルの読み込み開始からしばらくはほとんど変化せず、最後の方でぐっと使用量が増え、画面表示が完了したところでちょっと減る。 平坦な部分がXMLのDOMをHTMLの文字列に変換しているところで、メモリの使用量が増え始めるのがinnerHTMLによってHTMLのDOMが構築されるところだろう。

確実に完走できる約10MBのファイルを使い、処理の各所で時間計測してみたところ、各処理にかかる時間は処理順にだいたいこんな感じだった。

  1. XMLファイルを読んでDOMを構築するのに1秒以下。
  2. 構築したDOMの全要素にアクセスしinnerHTML文字列の配列を作るのに11秒。
  3. innerHTMLからHTMLのDOMへの変換に3秒。
  4. 変換したHTMLを実際に画面表示するのに9秒。

これらのうちの1と3と4には手の出し様が無い。 全体の半分程度とはいえ、手の出し様がある2に一番時間がかかっているのは、不幸中の幸いだったかもしれないな。

なんてことを考えなら、2の部分を改善するためにさらに処理時間の内訳を調べてみたら、11秒のうちの10秒がDOMの各要素の取得時間だった。 あらら、改善の余地無しじゃないか。