「完全被参照リスト管理」の実装を完了した

「完全被参照リスト管理」という仕組みを実装しているところだ.これが完成すれば,込み入った参照関係の網目が完全にアンダーコントロールに入ったことになる.この処理の追加コストが時間的・空間的にどのくらいのものになるのかが興味あるところだが,まだバグが取り切れていないのでもうしばらくかかりそうだ.ファイルを開くまでは順調に動作しているが,系統並び替えを実行するとPAIRLIST::cancelで被参照カウントの残留が発生する.何が残っているかは新設した完全参照リストをダンプするだけで確定できた.DATALISTクラスで参照を発生させないための改修を行っているのでその修正がまだ収束していない.

再現手順は構成図 TEST.zelをUNDOCHAINで開いてgraph3でソートだけだが,#43490 PAIRBOXで被参照カウントの残留が起きる.暫定的にBottomlist(NULL)の行をリスト要素の削除の前に実行するようにして解消した.この修正が正当なものであることはあとから見ることにしよう.引き続き同種のエラーが今度はTRIBELISTのcancelで発生する.ただし,このエラーの原因は明瞭だ.TRIBELIST::cancelの中からDATALIST::cancelを呼び出しているためだ.これをNLIST::cancelないしLIST::cancelとすれば問題は解決する.⇒解消した.PAIRLIST:cancelのループでリスト要素を削除する前にBottomlist(NULL)で要素への参照を解除することは妥当でありかつ必要である.

このサンプルでざっと包括テストを通してみよう.

上記サンプルを#29 PDBでソートして「逆転循環サイクルの枝を破棄」が発生し,SIMPLEEDGE::dumpでlinkにPAIRBOXが入っているために停止した.GENEBOX::CheckInverseCycleでは検査に枝グラフgraph1を使っている.このグラフの節点はPAIRBOXだ.グラフを作成するときにそのグラフのノードの型を設定できるようにしておくとよいのではないか?graph1は逆転循環検査にしか使っていないので名前も明示的なものにした方がよい.⇒InvertCycleGraphと呼ぶことにした.

SIMPLEGRAPHにはattributeという変数を持っているが,モノ/ヒモノの判別以外の用途には使われていないので,この領域に属性ではなくノードクラスのCIDを格納するとよいのではないか?そのグラフが何を扱っているかを特定することは有用だし,CIDが分かればモノ/ヒモノを判別することもできる.モノはCIDを持っているが,ヒモノの場合はたとえば-1などの値を入れればよい.もし,ヒモノもデータタイプごとに識別したいのであれば,負値の定数を与えればよい.⇒改造してみよう.デフォルトはnoduleタイプとし,原則つねにグラフのnew関数ではコンストラクタに引数を与えて呼び出すことにする.⇒この修正は一段落してから実施することにして,ここでは停止しないようにしておこう.

完全木テストを実施してみる.全体図テストと親族図テストを統合したものだ.⇒どうもさすがに描画が遅くなっているような気がする.これではリリース版には採用できないかもしれない.つまり,デバッグ用途にしか使えないということになる.まぁ,それはそれでそれなりの有用性はあると思われるが…現在のサンプルで計測したパフォーマンスは

TIME=0.193 validcardnum=66 validmargnum=24 nodecount=2967 lastsnum=64006  INTRASH=204 REUSE=60835 WASTE=61039 NEWOBJ=64006

のような数値だ.カード66,結婚24で系統並び替えに要する時間は0.2秒,アクティl部なオブジェクトは2967個,ゴミ箱に204個あり,生成されたオブジェクト総数(再利用を含む)は64006だ.これを完全参照リスト管理導入前と比較してみよう.一旦ここでバックアップを取っておく.⇒旧版に戻して実行してみた.

TIME=0.084 validcardnum=66 validmargnum=24 nodecount=580 lastsnum=805  INTRASH=72 REUSE=153 WASTE=225 NEWOBJ=805

この差は隠しようがない.時間効率で2.3倍,空間効率で5倍,ステップ数で80倍の差がある.これだけ大きな差があるとさすがに公開版に適用するのは無理があるのではないだろうか?完全参照リスト管理の有用性は後処理にあるが,最大の後処理はアプリ終了時の一回しか実施されないし,そのときにはユーザはそこでどれほど時間が掛かってもほとんど気にしないだろう.(この後処理は完全参照リストを使ってもっと効率化することはできるが,実効的にはほとんど有意性を認められない)

ロジックが分岐するのはメンテナンス上好ましくない.ロジック的には前者の方が整理されているし,後者は前者の特定ケースとして処理可能なので,前者を採用することでフィックスし,ReferenceControlだけをNOPにすることで対応するということにしよう.残念だが,仕方ない.ReferenceControlを止めると数字は以下のように変わる.

TIME=0.084 validcardnum=66 validmargnum=24 nodecount=580 lastsnum=805  INTRASH=72 REUSE=153 WASTE=225 NEWOBJ=805

導入前と完全に一致している.まぁ,これでよいのではないだろうか?これを現実として受け入れるしかないだろう.ところで,この改修(完全被参照リスト管理の導入)に取り組むことになったきっかけは何だったのだろう?きっかけは「抽象グラフ検証系の複線化」という修正の中で非参照カウントの残留が発生したという辺りのようだ.シューティングのために従来方式の参照リストを使おうとしたら不具合が起きたりしたためだろう.ことのついでに「NODULE,Bobject,DATALISTなどを「抽象クラス」として再定義する」というのをやってみよう.

クラスを抽象クラス化するには仮想関数の少なくとも1個を純仮想関数にする必要がある.まず,DATALISTクラスを見てみよう.ここには仮想関数は一つしかない.この関数は実装されているが,実際には何もしないでゼロを返しているだけなのでこれを純粋仮想関数とするのは容易だ.「DATALISTクラスの抽象クラス化@20201105」で修正してみよう.⇒実装したが,DATALISTのインスタンスを生成しているところがある.さっきまで掛かっていたReferenceControlだ.DATALISTの抽象化を止めるか,新たに派生クラスを作るしかない.

この参照リストを作るためにNODEREFという構造体を新設しているが,その代わりに参照リストクラスというクラスを新設するというのがもっとも正しい.REFLISTというリテラルはすでに使われているので,NODEREFLISTとしてみよう.クラスを追加登録するためには,まず,nodule::getClassNameにCIDとクラス名文字列を登録しなくてはならない.以下のように登録した.

case ‘x’: s = “NODEREFLIST”; break;    // < ‘h’ LIST

同様の内容をnodule.hのコメント欄にも記録する.あとはCOMMONHEADERPARTマクロがすべてやってくれる.LISTにも仮想関数の実装が必要だ.これで使えるようになった.いや,まだある.CleanSansyo,Dispose,dumpの3つの関数だけは実装しなくてはならない.これらはCOMMONHEADERPARTの中で定義されている.NODULEファミリィの共通ヘッダ定義にはCOMMONHEADERSHORTとCOMMONHEADERPARTがあるが,どう違うのだろう?

COMMONHEADERPARTはCOMMONHEADERSHORTに上記3つの関数を追加したものだ.これらを必要としないクラスはCOMMONHEADERSHORTを選択することができる.COMMONHEADERSHORTを使っているクラスには,BASETABLE,NLIST,nlist,ARRAY,PSCLASSがある.クラス定義内で実装する必要がある場合にそうしているのではないか?テンプレートクラスの場合はその方が対処し易い.NODEREFLISTも手抜きしてCOMMONHEADERSHORTを使うことにした.

実行できるようになった.修正を戻した版も実行可能であることを確認しておこう.⇒問題なさそうだ.こんどはBobjectを仮想化してみよう.Bobjectには仮想関数はたくさんあるが,実装を持たない派生クラスも結構ありそうな気がする.Bobject::Drawという関数はどうか?おそらくこの関数はほとんどすべての派生クラスで実装しているはずだ.⇒中には持っていないものもある.たとえばTREEVIEW,というかこれだけだ.逆にRefreshという関数を持っているのはTREEVIEWだけだ.Refreshを廃止してDrawに統一すればよいのではないか?

Refreshを廃止してもコンパイルエラーは出ない.おそらくRefreshを使っているのはTREEVIEWだけだが,TREEVIEW::Refreshはシグネーチャが違うのでエラーにならない.この関数は引数を3つ持っているが,最後のmapmodeをMAP_DISPLAYで定数化したバージョンをTREEVIEW::Drawとしておこう.⇒これでビルドできた.

しかし,動作上の問題が出てきた.TREEVIEW::Refreshはデバイスコンテキストが空で呼び出されることを認めていないが,DrawがRedrawからcdc空で呼び出される場合がある.TREEVIEW::Refreshはあくまで実描画を目的としているので,TREEVIEW::DrawにはBobject::Drawの内容を移しておくことにする.⇒動作するようになった.少なくとも現行ではTREEVIEW::Drawは実描画には使われていない.修正を戻して動作確認する.⇒OKだ.

noduleは実装クラスだが,NODULEは仮想化できるはずだ.適当な仮想関数があるだろうか?仮想関数は山のように持っているのだが…void setpid(void)はどうか?この関数はCOMMONHEADERSHORTで定義されているのでほとんどすべてのクラスで実装されている.NODULEもこの関数の実装を持っているが,これはコンストラクタの中で一度実行されるだけなのでコンストラクタに内容を転記しておけばよいはずだ.

実装した.動作している.setpidは共通ヘッダ部定義で定義されている他はnoduleにあるだけだ.つまり,共通ヘッダ定義を持たないクラスはNODULEとnoduleだけと考えられる.NODULEは別としてnoduleが共通ヘッダ定義を使わないというのはなぜだろう?noduleは共通ヘッダ定義の関数をほとんど自前で持っているように見えるので,なぜ共通ヘッダ部定義を使わないのか理由が分からない.noduleクラスにヘッダ定義を持ち込むとどういうことになるのか調べてみよう.

共通ヘッダ定義と重複する関数を除くと,未定義変数が3つ現れる.pclassid,vsfunctable,VSLOTFUNCだ.また,nodule::_SLOTが非標準の構文とされる.⇒これらの定義がnoduleのクラス定義より後方に置かれていたためだ.DrawTreeView.cppまでコンパイルするとまたnodule.hでエラーが出始めた.

nodule::operator new’: すべてのコントロールのパス、関数を回帰するとランタイム スタック オーバーフローが発生します。

このエラーはDrawTreeView.cppで始めて発生し,それ以外のファイルでは何も起きない.MakeTribeBox.cppでも出た.RestoreTable.cpp,nodule.cppでも出る.⇒最終的にエラーが出るのはDrawTree.cpp,MakeTribeBox.cpp,RecycleSystem.cpp,ZelkovaDLL.cppだけになった.ただし,これらのファイル名がプリントされているということはコンパイル完了とみなされるので,おそらくこれらのファイルのあとにコンパイルしたファイルでエラーが出ているものと思われる.

このエラーの原因は明瞭だが,なぜこのエラーが出るファイルと出ないファイルに分かれるのかは調べておいた方がよい.C++ファイル80本のうちオブジェクトファイルができていないもの26個あった.共通点はよく分からないがBobject.cppを含め,Bobjectクラスのファイルが多いような気がする.⇒※動作から推定すると,*.hの検査と*.cppのコンパイルは別スレッドで同時進行しているように思われる.

void *nodule::operator newとnodule::operator deleteを外した共通ヘッダ部を作ってCOMMONHEADERBASICとし,COMMONHEADERSHORTはこれにこれらの関数を追加したものという構成に変えて,noduleクラスではCOMMONHEADERBASICを展開するようにした.nodulleのoperator newとdeleteをNODULEに移管できない主な理由は,オブジェクトのリサイクルをnoduleが担当しているためだ.リサイクル操作はnewとdeleteの中で実行されるので,このような仕掛けが必要となっている.

これまではnoduleは他のNODULEクラスとはまったく異なる扱いになっていたが,この整理によってoperator newとdeleteを除けば,他のクラスと全く同じ同じ構成を持つNODULEの標準的クラスとして位置付けられるようになった.修正を戻しても動作することを確認した.

SIMPLEGRAPH::attributeを廃止し,グラフのノードから参照するオブジェクトのクラスのCIDを格納するように仕様変更する.CIDが正のときは通常のnoduleクラス,負値の場合は-CIDサイズのデータが格納されるものとする.⇒実装した.

本日4回目のバックアップを取っておこう.これまでの修正をいくつかフィックスしておくことにする.まず,「SIMPLEEDGE_generationを廃止@20201020」と「NODULE:nodegeneを廃止する@20201021」は確定でよいだろう.⇒ダメだ.修正に失敗してしまった.もう一度やり直そう.大きいブロックを消すときは一旦隠蔽してから削除した方が安全だ.前者が18ヶ所,後者が47ヶ所ある.失敗すると痛いのでまた取っておこう.⇒また失敗だ.未解決の外部参照が2つも出てしまった.CARDLINK::getnodegene(void)とCARDLINK:setnodegene(int)だ.今日はもう寝たほうがいいのかもしれない…

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA