OutOfMemoryExceptionにどう対処したらよいか?

デバッグモードでCollatz3950-10934.ADLを開いて保存しようとして,KAKEIZU::saveFamilyBaseでERR_NOMEMORYメモリ不足エラーが発生する.ヒープサイズは4294967296をリザーブしている.これでも足りないのだろうか?⇒メモリ不足はファイル書き込み用バッファのメモリ要求で発生している.要求サイズは45,904,944,このときまでに使用しているメモリ総量は106,632,555で,トータル152,537,499.これはヒープサイズの4,294,967,296より,ずっと小さい.おそらくメモリが断片化して連続した領域を確保できないためだろう.

まとめて書き込むのではなく,一行づつ書き込むことにすれば,それほど大きなバッファは必要ないのではないだろうか?⇒現行では,系図木処理から直接ファイルに書き込むのではなく,QUICKDBという簡易データベースシステムを経由して入出力するようになっている.QUICKDBではカードデータベース,結婚データベース,記録データベースを管理して,保存時にはそれらを一括して書き出している.バッファは一度確保されると,サイズオーバーにならない限りそれを使い続けるので,それほど効率の悪いことをやっている訳でもない.QUICKDBは外部ストレージとの入出力用にBUNSFILEというクラスを使っている.

KAKEIZUは人名データベースと結婚データベースという2つのQUICKDBを持っている.QUICKDBはBUNSFILEをメンバーとして持ち,BUNSFILEの中にバッファを確保している.また,これとは別にCOUPLINGにはCHUNKFILEというメンバーを持っている.CHUNKFILEは独立に記録データの入出力用としてFILEBUFFというバッファを持っている.CHUNKFILEはMFCクラスのCArchiveと連携している.⇒BUNSFILEのバッファを常設とし,起動時に一度生成してそのまま使うことにすれば少しは改善されるのではないか?⇒やってみよう.⇒対処した.最初に確保してしまえば,その後はメモリ操作不要になる.

新規ファイルを開いた状態からCollatz3950-10934.ADLをインポートでOutOfMemoryExceptionが発生する.このエラーはVBで発生している.何か対策はあるだろうか?BuildNewTableではDataGridView.Rows.Clearでコレクションを空にしている.これを削除ではなく,更新にすることは可能だろうか?⇒UpdateTableという関数もある.UpdateTableではレコードの更新を行う.行数が足りないときには追加することができるので,これでもカバーできるのではないか?⇒いや,効果ないだろう.新規ファイルから初めているので,一覧表は元々空の状態だ.つまり,絶対的に不足しているというしかない.

不要な列は表示しないというのは効果があるだろうか?ダメのようだ.しかし,テーブルを最初からそのようなものとして構築してやればかなり効果はあるのではないか?MAXITEMを変えればそのような状況になるが,至るところで「インデックスが配列の境界外です」になるのは避けられない.テーブルを操作するほとんどすべての関数で起きるだろう.エラーが200箇所以上発生する.⇒MAXITEMを調整してテーブルを縮小するというのはかなり難しい.そもそもADLのインポートはテーブルを媒介としているので,無理がある.

検証テストのテストカウントを3900としてもう少し小さなサンプルを作った.カード数は6165,結婚数4684になった.これなら楽にロードできそうだ.⇒ロードしたADLを名前を付けて保存で,BUNSFILEのバッファが空になっている.CloseFamilyBase→COUPLING::Cleanで空になっている.⇒Cleanの代わりにdoCleanを使うことで解消した.doCleanは自己の固有データ部のクリーンのみを実行する.CleanではdoCleanを実行し,かつ下位スロットをCleanしている.多少疑問の残るところだが,一応これでよいことにしておく.しかし,このサンプルは反例になっていない.ループカウント30で脱出してしまう.

Collatz3950-10934.ZELを開いた後,Collatz3950-10934.ZELを読み込もうとしてQUICKDB::lodquickで例外が発生した.アクセスヴァイオレーションが発生している.BUNSFILEのバッファは保持されている.⇒どうもこのファイルはこわれているようだ.ファイル冒頭のマジックナンバーを読み込むことができない.ファイルサイズも48KBしかない.本来なら3MBくらいはあるはずだ.多分ファイル保存操作中エラーが発生して中断になっているのだろう.

Collatz3950-10934.ZELを開いた後,Collatz4000.ADLをインポートしてTITLELINK::SetTitleInformationで(len != TitleInfo.Rtfsize)というエラーが出た.⇒その後,SetRowValue→BuildNewTable:rowindex=5003でOutOfMemoryExceptionになった.このサンプルはカード数12243, 結婚数10170で,カードテーブルサイズ=12300,結婚テーブルサイズ=10300という設定で走っている.⇒名前を付けて保存はできた.⇒メモリ不足が関係している可能性はある.

目下の懸案となっているのは以下の2点だ.①UNDOでShadowが空というエラーが起きている,②Collatz4000-12243.ZELでループカウントオーバーが起きる.上記のCollatz4000.ADLというサンプルは②の反例に該当する.これを極小反例サンプルの抽出に掛けたいところだが,メモリ不足になるというのが最大の問題だ.DLLで発生するメモリ不足に関しては何らかの抜本策も考えられるが,VBで起きているメモリ不足に関してはほとんど対策がないか限られている.一覧表をまったく生成しないことにすればかなり効果があるのではないかと思われるのだが,隣接リストのインポートではカードテーブルをデータの受け渡しに使っているので,それもかなり難しい.

ファイルから読み込んだ1行データをテーブルのレコードに変換しないで,テキストのまま保持するということはできるのではないか?もし,それが可能ならかなりメモリ使用量を削減できるような気がするのだが… ImportFileでは実際それをやっている.インポート自体はテーブルを使わずに実行できることは立証されているので,一覧表にレコードを追加しないで空のまま放置というのでよいのではないか?⇒なんとかこれで動くようになった.

REDLINEは50という設定でも1点検査するのに相当な時間が掛かり,空振りばかり続くのでもう少しヒット率を上げるために,子どものいないノードをピックアップしてテストすることにした.多分これなら時間とともに削減が進むだろう.⇒リリースモードに切り替えたら,大分速くなった.⇒速いはずだ.対象サンプルが違う.⇒そもそも,まだ動いていない.⇒ようやく動くようになったが,やはり,UNDOでエラーが発生している.これはおそらく,UNDOに関わる修正がまだ収束していないことを示すものと考えられるので,UNDO操作に集中してもう少し小さいサンプルで動作確認を行うことにする.

最小反例321ZELを開き,任意のカードを削除してUNDOBASE::SetUndoListで (!copynode->shadow)エラーが発生する.copynodeはUNDOの上書き対象ノードなので,すでにshadow付きになっているはず…⇒いや,copynode不在で新規に生成されたケースだ.できたてなのだから,shadowが入っていないのは当然なのではないか?少し古い版のコードを見る必要がある.⇒2022-02-26で見ると,/* 生成したばかりでshadowを持つはずがない@20170823 */というコメントが入っている.この位置にはshadowを設定するコードが入っているが,現行版では「1行削除@20220423」となっている.なぜだろう?かなり考え難い.※⇒編集ミスではないだろうか?

2022/04/23の修正は,「UNDOのShadowをfreeblock化」だ.shadowはフリーメモリなのだから,どこかでメモリを確保していなくてはならないはずだが,やっていないように見える.従来論理ではfreeblock::getmemでメモリを取得しているが,freeblockとして扱うためには,_getmem_を使う必要がある.このとき,MMTYPEを指定するようになっていて,MM_SHADOWという定数が新規に追加されている.⇒定義されているだけで,まったく使われていない.⇒修正した.(この修正は入れた記憶はあるが,誤って消してしまったのだろう)

一応,カード削除でUndo/Redoができるようになったが,カード数の表示が正しくない.Undoしても数字が元に戻らない.この数字はUpdateMaxCardで更新しているのだが…UpdateMaxCardの呼び出しが掛かって来ない.⇒「極小反例サンプル」対応でコストの掛かる処理を止めているためだ.①GetNewTable,②GetNewCard,③CenteringCardSubをパスしているが,おそらく,GetNewCardでUpdateMaxCardを実行していたものと思われる.⇒「極小反例サンプル検定の時間効率改善20220501」で対処した.

複数カードを選択して,逐次削除→全削除を実行→Undoして,UNDOSYSTEM::UndoProcessで(UndoCurptr->UndoNumber != UndoNumber)により停止した.2点選択して逐次削除→Undo→Undoで再現する.UndoCurptrのUndoNumberは1,UndoNumberは2になっている.明らかに大域変数のUndoNumberの方が誤っている.UndoしたときにはUndoNumberはデクリメントされなくてはならないはずだ.UndoNumberはUNDOBASEとUNDOSYSTEMで重複して調整されている.しかも,反対方向に調整している.どちらか一方は不用と思われる.UndoCurptrはnoduleクラスでも定義されている.UNDOCOMMANDはそれ自体noduleなので,混乱が生じている.noduleの側はUndoNumberとし,UNDOCOMMANDでは大文字のUndoNumberを使うようにしてみよう.⇒収まったようだ.

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA