オブジェクト型は,メモリに存在するオブジェクトに対する参照です。オブジェクト型を変数に代入したり,パラメーターとしてメソッドや関数に渡した場合,オブジェクトのコピーが作成される代わりにオブジェクトの参照カウントがインクリメントされます。オブジェクトを参照している変数に別のオブジェクトが代入された場合,あるいは変数やパラメーターがクリアされた場合,オブジェクトが消去される代わりにオブジェクトの参照カウントがデクリメントされます。参照カウントがゼロに達すると,オブジェクトが占有していたメモリが解放されます。
オブジェクト型またはコレクション型をオブジェクト型のプロパティまたはコレクション型のメンバーに代入した場合,代入しようとしているオブジェクト型またはコレクション型が代入されようとしているオブジェクト型またはコレクション型から直接的あるいは間接的に参照されているかどうかをチェックする必要があります。つまり,再帰的な参照の有無を調べる必要があります。再帰的な参照をきちんと管理しないと,正しいタイミングで参照カウントがゼロにならないためです。
代入の対象となっているオブジェクト型の一部分(たとえば上位のプロパティ)をすでに参照しているようなオブジェクト型またはコレクション型をオブジェクト型のプロパティまたはコレクション型のメンバーに代入した場合,参照の数に応じて代入に要する時間が長くなります。
$oMap:={}
For ($i; 1; $iterations)
$o:={col: []; o: {}}
$o.map:=$oMap
$oMap[String($i)]:=$o
End for
この例では,親オブジェクト$oMap
に対する参照がすべての子オブジェクトが持つ.map
プロパティに代入されているので,子オブジェクトを追加するたびに親オブジェクトの参照が増えてゆき,代入のスピードがどんどん遅くなってゆきます。それぞれの子オブジェクトは,{col: []; o: {}}
,つまり,1
個のオブジェクトと1
個のコレクションからなるシンプルなオブジェクト型としてインスタンス化されますが,.map
プロパティに親オブジェクト$oMap
を代入するためには,まず,代入しようとしている親オブジェクト$oMap
が代入されようとしている子オブジェクト$o
から参照されているかどうかをチェックする必要があるためです。回数を重ねるごとに親オブジェクトが持つ参照の数は増えてゆき,直接的な参照だけでなく,間接的な参照もすべてチェックしなければならないので,このチェックに要する時間はどんどん長くなります。もっとも,$o
はインスタンス化されたばかりのオブジェクトであり,すべての参照をチェックしたところで$oMap
からの参照はありません。相互に対する参照が生まれるのは,親オブジェクトを子オブジェクトを代入した後のことです。
つづく代入文でも,代入しようとしている子オブジェクト$o
が代入されようとしている親オブジェクト$oMap
から参照されているかどうかをチェックする必要がありますが,このときのチェックは瞬時に終わります。$o
の.map
プロパティが親オブジェクトを参照しているので,これは再帰的なオブジェクトです。
このことを理解すると,代入の順序を入れ替えるだけで,実行の速度が劇的に向上することに気づきます。
$oMap:={}
For ($i; 1; $iterations)
$o:={col: []; o: {}}
$oMap[String($i)]:=$o
$o.map:=$oMap
End for
子オブジェクト$o
を発展させる前に,まず,親オブジェクトに代入して,再帰的な参照を確立させているのがポイントです。この時点で$o
には,メンバーである1
個の空オブジェクトと1
個の空コレクション,そしてオブジェクトそのものを加えた合計3
個の参照しかありません。そして再帰的な参照もありません。なのでチェックはすぐに終わります。つぎに子オブジェクトの.map
プロパティに親オブジェクト$oMap
を代入しますが,この時点で両オブジェクトはすでに互いを参照しているので,やはりチェックは瞬時に終わります。
つまり,再帰的な参照を持つオブジェクト型またはコレクション型を代入文で構築する場合,まず部品を完成させてから本体に連結するのではなく,さきに参照だけ連結させてから個別の部品を「肉付け」したほうが良い,ということになります。オブジェクトの参照が少ないうちに再帰的な代入をしたほうが,オブジェクトの参照が多くなってから代入するよりも効率的だからです。