朝、卵焼きを作ろうと卵を割ったら、シャム双生児風だった。
いつも買っている卵はやや大きめで、そのせいか黄身が双子の確率が結構高い。 しかしこれまでに何度か遭遇した双子はどれも、二つの黄身がきっちり分離していた。 こうして半分くっついた双子は、俺的には珍しいのだが、世間一般ではどうなんだろう。
そして、この塗りの剥がれたお碗を、俺はいつまで使うのか。
昨日の続き。
メモリ不足をどう回避するかを考えていたのだが、更にその先を考えてみれば、仮に String.replace() で動いたとしても遅いんだよな。 特に第2引数をfunctionにした場合は激遅い。
ということで、SAX風という基本路線はそのままに String.replace() ではなく String.substr() を使ってみることにした。
String.replace() が置換した結果を新しい文字列として作成するのに対し、 String.substr() は元の文字列の参照開始位置をずらしたものを返すので、速い上にメモリの使用効率も良い。 よく判らないメモリ不足を、これなら回避できるのではないか。
function doLoad() {
try {
document.getElementById( "base" ).innerHTML = xmlToHtml( 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 iStrm = new ActiveXObject( "ADODB.Stream" );
var enc = loadFromFile( "Shift_JIS", -2 ).match( /\s+encoding="([^"]+)"/ );
return loadFromFile( enc ? enc[ 1 ] : "Shift_JIS", -1 );
function loadFromFile( encoding, mode ) {
iStrm.Charset = encoding;
iStrm.Open();
iStrm.LoadFromFile( filename );
var buf = iStrm.ReadText( mode );
iStrm.Close();
return buf;
}
}
function xmlToHtml(xml) {
var rInstruction = new RegExp( /^<\?(.+?)\?>/ ); // 1:宣言内部
var rDoctype = new RegExp( /^<!DOCTYPE\s+([^\s].+?)>/i ); // 1:宣言内部
var rComment = new RegExp( /^<!--([\s\S]*?)-->/ ); // 1:コメント
var rCData = new RegExp( /^<!\[CDATA\[([\s\S]+?)\]\]>/i ); // 1:CDATA内部
var rText = new RegExp( /^(([^<]|[*s])+)/ ); // 1:テキスト全体
var rTag = new RegExp( /^<([^\?!][^>]*(\s*("[^"]*")*\s*)*?)>/ ); // 1:<>内部
var rNotBlank = new RegExp( /[^\s]/m );
var rAttributes = new RegExp( /\s+([^\s="]+)\s*=\s*"([^"]+)"/g ); // 1:属性名, 2:属性値
var htmlAry = [];
var buf = xml;
while ( buf !== "" ) {
var len = buf.length;
buf = match( buf, rInstruction, instructionHandler );
buf = match( buf, rDoctype, doctypeHandler );
buf = match( buf, rComment, ignore );
buf = match( buf, rCData, cDataHandler );
buf = match( buf, rTag, tagHandler );
buf = match( buf, rText, textHandler );
if( len === buf.length ) {
throw "ERROR : unsupported element";
}
}
return htmlAry;
function match( s, regexp, handler ) {
var matched = s.match( regexp );
if ( matched ) {
handler( matched[ 1 ] );
return s.substr( matched[ 0 ].length );
}
return s;
}
function ignore() {}
function instructionHandler( s ) {
htmlAry.push( "<div><?" + s + "?></div>" );
}
function doctypeHandler( s ) {
htmlAry.push( "<div><!DOCTYPE " + s + "></div>" );
}
function cDataHandler( s ) {
htmlAry.push("<div><![CDATA[<div>" + s.replace("<", "<").replace(">", ">") + "</div>]]></div>");
}
function textHandler( s ) {
if ( rNotBlank.test( s ) ) {
htmlAry.push(s.replace( "<", "<" ).replace( ">", ">" ) );
}
}
function tagHandler( s ) {
if ( s.charAt( 0 ) === "/" ) {
endTagHandler( s );
} else if( s.charAt( s.length - 1 ) === "/" ) {
emptyTagHandler( s );
} else {
startTagHandler( s );
}
}
function startTagHandler( s ) {
htmlAry.push( "<div><" + setClassToAttribute( s ) + ">" );
}
function endTagHandler( s ) {
htmlAry.push( "<" + s + "></div>" );
}
function emptyTagHandler( s ) {
htmlAry.push( "<div><" + setClassToAttribute( s ) + "></div>" );
}
function setClassToAttribute( s ) {
if ( rAttributes.test( s ) ) {
return s.replace( rAttributes, ' $1="$2"' );
}
return s;
}
}
}
昨日との大きな違いは、 String.replace() を繋げていたところが match() の繰り返しになったこと。 このmatchの中で
と、SAX風の肝となる処理をしている。
そもそも String.replace() を選択したのが記述が楽だからで、だから String.substr() にしたら面倒になるかと思ったのだが、その面倒な部分を関数に隠蔽してしまえば、全体的には若干面倒って程度で大差無いな。 いや、ハンドラーの方は長さ0の文字列を返す記述が不要になったし、むしろスッキリしたかもしれない。
そして動作確認。
これが予想外に速い。 そして完遂率も高い。 SAX風処理内でのメモリ不足は発生しない。
せっかくだから、昨日の即死版はちょっと忘れて、これまでの各バージョンに約10MBのテスト用XMLファイルを読ませて処理時間を比較してみた。
load() 内の最初と最後で時刻を取得して時間差を出してみると、こんな感じ。
月.日 | 処理時間(秒) |
---|---|
02.03 | 40 |
02.04 | 14 |
02.06 | 7 |
割と良い感じで日々改善されているではないか。
表中の下の2つは、どちらも処理時間のうちの3秒ほどがinnerHTMLの文字列をHTMLのDOMに変換するのに使われている。 また、どのバージョンでも、この処理時間の後におよそ10秒かかってHTAの画面のレンダリングが完了する。
つまり今のバージョンだと、改善の余地はあと4秒分ぐらいしかなくて、しかもそこはもうボトルネックではないのだな。
ということで、予想外に難航した doLoad() はここまで。 続きは明日。