2017年3月22日水曜日

「innerText = ''」にしても元の中身を参照している変数があるとメモリ解放されない

エレメントに限りませんが、メモリ上に展開しているデータはどこからも参照できない状態になると始末されてメモリの空き容量が増えます。
document.body以下のDOMエレメントは「document.body.innerText = ''」とすることで画面上からは消え去りますが、innerTextを変更する前に「var elem=document.body.firstChild」などを実行していた場合は、innerText変更後でも「elem」変数の中には元のエレメントが存在します。つまり、メモリは解放されていません。

意図せず参照が残って解放されないものが溜まっていくとメモリ不足になってPCの動作がモタついてしまいますが、具体的にどのくらい消費してしまうのか確認しました。

「<div></div>」という、中身が何もない素のエレメント一個で大体200KBぐらい増えていました。意外と大きいです。

試験用ソース
<html>
<title>試験</title>
<body>
<table id=tbl border=1><tr><td></td><td></td></tr></table>
<input id=inp0>
<input id=inp1>
<input id=inp2>
<input id=inp3>
<div id=div></div>
</body>
<script>
resizeTo(400,300)
onload=function(){
var 試験回数=10000, バラツキ確認回数=100, arrS=[], arrE=[], arr解放後=[], arrElem=[], aEi=0
試験(バラツキ確認回数,function(){
arrS.push(getメモリ使用量())
for(var i=0; i<試験回数 ;i++){div.insertBefore(arrElem[aEi++]=document.createElement('div'))}
arrE.push(getメモリ使用量())
div.innerText = ''
arr解放後.push(getメモリ使用量())
},
function(){ },
function(){ },
function(){
var arr差ES=[], arr差E解=[], sumES=0, sumE解=0
for(var i=0,L=arrS.length; i<L ;i++){
sumES += arr差ES [i] = arrE[i] - arrS[i]
sumE解 += arr差E解[i] = arrE[i] - arr解放後[i]
}
var obj={
試験回数:試験回数,
バラツキ確認回数:バラツキ確認回数,
'arrE[n]-arrS[n].max':Math.max.apply(null,arr差ES),
'arrE[n]-arrS[n].min':Math.min.apply(null,arr差ES),
'arrE[n]-arrS[n].average':(sumES/L),
'arrE[n]-arr解[n].max':Math.max.apply(null,arr差E解),
'arrE[n]-arr解[n].min':Math.min.apply(null,arr差E解),
'arrE[n]-arr解[n].average':(sumE解/L)
}
var tbo=tbl.firstChild, tr=tbo.firstChild.removeNode(true), tds, newTR
for(name in obj){
tbo.insertBefore(newTR=tr.cloneNode(true))
tds = newTR.childNodes
tds[0].innerText = name
tds[1].innerText = obj[name]
}
inp0.value = arrS
inp1.value = arrE
inp2.value = arr解放後
inp3.value = ''
setInterval(function(){
inp3.value += getメモリ使用量()+','
},10)
setTimeout(function(){
arrElem=null
},5000)
})
}
// document.body.onclick=function(){ document.title = getメモリ使用量() }
試験=function(バラツキ確認回数, fun試験, fun試験前, fun試験後, fun全行程終了後){
var 実施回数=0
setTimeout(function(){
setTimeout(function(){ fun試験前() },0)
setTimeout(function(){ fun試験 () },0)
setTimeout(function(){ fun試験後() },0)
実施回数++
if(実施回数<バラツキ確認回数){setTimeout(arguments.callee, 0)}else{ fun全行程終了後() }
},0)
}
getメモリ使用量=function(){
var res = new Enumerator(GetObject("winmgmts:root\\CIMV2").ExecQuery("SELECT * FROM Win32_Process where ProcessID="+getPID()))
return res.atEnd() ? (void 0) : res.item().PageFileUsage
}
getPID=function(){
var f=function(){
// 子プロセスをcscriptにすると黒い画面が一瞬表示されてしまう。
// 「//B」オプションを付けないとwscriptの設定画面が表示されてしまう。
var childPID = (new ActiveXObject('WScript.Shell')).Exec('wscript //B').ProcessID
// 子の親(自身)のPIDを特定する
var res = new Enumerator(GetObject("winmgmts:root\\CIMV2").ExecQuery("SELECT * FROM Win32_Process where ProcessID="+childPID))
return res.atEnd() ? (void 0) : res.item().ParentProcessID
}
// 1度目で取得に成功すればその値を返し、ダメなら2回目の処理を実行する。
return f() || f()
}
gt=function(){return (new Date()).getTime()}
loop試験 = function(回数, fun){
for(var s=gt(),i=0;i<回数;i++){ fun() }
return gt() - s
}
</script>
</html>



試験結果




グラフ




参照を保持しているarrElemに[]を代入するタイミング(サイクル完了後5秒ぐらい)より前のタイミング(サイクル完了後3.2秒ぐらい)で78MBぐらい減っている理由は不明です。

100万個も参照を溜め込むと200MB弱もメモリを使用してしまいます……が、そんなに沢山のエレメントを扱うプログラムを作る機会があるのか不明です。
200MB / 100万個 = 約200KB

64bitOSを利用している場合は無視して良い場合もありそうですが、32bitOSで利用することが皆無ではない状況なら少し気を付けた方が良さそうです。

0 件のコメント:

コメントを投稿