* メモリ管理 #2
-(by [[K]], 2022.02.07)

** (1)
-''AMemAlc0'' (シンプルなメモリアロケータ:サイズは固定)
-''AMemAlc1'' (サイズ指定できるメモリアロケータ)
-''AMemAlcSys'' (malloc, freeを呼ぶだけのメモリアロケータ:サイズは固定)
-''AMemAlc00'' (AMemAlc0とAMemAlcSysの内部インタフェース共通化のためだけの構造体)
-''AClean'' (freeやデストラクタの呼び出しを自動化)

** (2)
-''AMemAlc0'' (シンプルなメモリアロケータ:サイズは固定)
--[説明]init時に決めたサイズのメモリを管理する。手持ちのメモリがなくなると、親のアロケータを呼び出してメモリを補充する。
--void  AMemAlc0_init()
--void  AMemAlc0_deinit()
---未返却のメモリが一つでもあるとdeinitできない(ADBGLV>=1のとき、実行時エラーとなる)。
--void *AMemAlc0_alc()
--void  AMemAlc0_fre()
---当たり前だけどalcで得たメモリ以外をfreしてはいけない(ADBGLV>=2のとき、実行時エラーとなる)。
---二重freも当然やってはいけない(ADBGLV>=2のとき、実行時エラーとなる)。
--AMemAlc0 *AMemAlc0_init1()
---AMemAlc1から指定したサイズのAMemAlc0を作る。

-''AMemAlc1'' (サイズ指定できるメモリアロケータ、ほぼmallocの代用)
--[説明]8, 16, 32, 64, ..., 1Gの合計28個のAMemAlc0を持っていて、要求サイズに応じてアロケータを切り替えてメモリ管理を実現。
--void  AMemAlc1_init()
--void  AMemAlc1_deinit()
---未返却のメモリが一つでもあるとdeinitできない(ADBGLV>=1のとき、実行時エラーとなる)。
--void *AMemAlc1_alc()
--void  AMemAlc1_fre()
--AMemAlc0 *AMemAlc1_getAlc() (サイズを指定すると対応するアロケータを返す)
--void  AMemAlc1_report() (現在の利用状況を画面などに出力する)

-''AMemAlcSys'' (malloc, freeを呼ぶだけのメモリアロケータ:サイズは固定)
--void  AMemAlcSys_init()
--void  AMemAlcSys_deinit()
--void *AMemSlcSys_alc()
--void  AMemSlcSys_fre()

-''AClean'' (freeやデストラクタの呼び出しを自動化)
--AClean *AClean_init()
--void  AClean_deinit()
--ACleanDat *AClean_set1()
--ACleanDat *AClean_set2()
--void  AClean_out()
--void  AClean_force()

** (3)
-[1]ADBGLVはデバックレベルで、コンパイル時に-DADBGLV=2のように指定する。デフォルトは0。ADBGLV=0のとき、aclライブラリは一切のデバッグ支援用の処理を省略する(=リリースモード)。ADBGLV=1だと、処理速度がほとんど落ちない処理だけを有効にする。ADBGLV=2だとすべてのデバッグ支援機能を有効にする。・・・これにより、デバッグ支援を開発者のニーズに合わせて利用できる。
-[2]AMemAlc1は基本的に2のべきのサイズのメモリブロックのアロケートが最も効率よくできるので、内部の管理ノードなど(=たくさんalcするもの)はできるだけこのサイズに調整しておくほうが良い。それが難しい場合は、自分が必要とするサイズのAMemAlc0を作ってしまうとよい。そうすればメモリ効率はほとんど悪化しない。
--実験してみた(実験結果は32bitモード時のもの)
 AClean c;
 AClean_init(&c, ma1, 0);
 ARep(10000) AMemAlc1_alc(ma1, 24, &c);
 AMemAlc1_report(ma1, stdout);
 AClean_out(&c);
 // これだと n=10323 total=402688 になる.
 // 32*10000(24*10000由来)+256*323(Cleanの内部データ由来,256バイトで31件登録できる).
 // 合計で402688バイト、10323個になる.
 // 24*10000を使うために、32*10000が使われたので、効率は75.00%になる.
 // 当然だけど、AClean_out()後は0に戻る.
 
 AClean c;
 AClean_init(&c, ma1, 0);
 AMemAlc0 alc24;
 AMemAlc0_init1(&alc24, 24, ma1, 16 * 1024, &c); // ma1から16KBずつとってきて24バイトに切り分けて提供するアロケータを作る.
 ARep(10000) AMemAlc0_alc(&alc24, &c);
 AMemAlc1_report(ma1, stdout);
 AClean_out(&c);
 // これだと n=339 total=329472 になる.
 // 16k*15(24*10000由来)+256*323(Cleanの内部データ由来,256バイトで31件登録できる)+1k*1(alc24の内部管理データ).
 // 合計で329472バイト、339個になる.
 // 24*10000のために、16k*15+1K*1が使われたので、効率は97.25%になる.
 // 当然だけど、AClean_out()後は0に戻る.

-[3]今のところ、AMemAlc0とAMemAlc1にはかなり満足しているが、ACleanにはまだ少し不満がある。もっと少ないメモリで実現できないだろうか?・・・まあでもこれ以上頑張ると複雑になるので、やはり今ぐらいでちょうどいいのかもしれない。
-[4]ACleanにデストラクタやfree呼び出しを任せてしまえばプログラムは確実にシンプルになるが、量が多くなると消費メモリが多くなってくるので、量が多くなりそうなものは手動でデストラクタ呼び出しやfreeなどを行った方がいい。・・・手動でやると間違えてしまうこともあるかもしれないが、そんなときはADBGLVを2にしてしまえばほとんどのバグは見つけられるので、状況はそれほど悪くないだろう。
-[5]木構造などを構築する際に、ノードを専用のメモリアロケートで確保しているとする。この木をすべてdeinitする際には、ノードをいちいちfreeせずに、専用のメモリアロケータを強制的にdeinitしてしまえば、メモリはすべて上位のアロケータに返却される。むしろそのほうが高速ですらある。もちろんfreeしてからdeinitしてもいいし、途中までfreeしてからdeinitしてもよい。

-[6]longjmpとかでAClean_force()が重要になるということを考えると、ACleanを使わないでfreeやデストラクタを自前で呼び出す処理は良い書き方とは言えなくなる。自前でやることはより効率よくかける余地が出てくるのでいいのだけど、それはその後片付け処理を別関数に切り出して、その関数をACleanに登録しておくべきだろう。そうすれば、高効率とlongjmp利用可能が両立できる。

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS