ようやく一歩前進です!

Live2DをC++で使うためにメモリを確保したり解放したりするもの(アロケータ)を用意しないといけない問題にぶち当たっているのは自分だけかもしれませんが、完成しました!

Live2Dではメモリを確保する際にアドレスの位置を指定された数の倍数に合わせること(アライン)が要求されるので、その対応を含めて今回作成したアロケータの構造をここにメモメモしようと思います。



メモリアロケータの構造については↓の説明を参考に勉強させていただきました。

mtrebi/memory-allocators: Custom memory allocators in C++ to improve the performance of dynamic memory allocation



今回はスタックアロケータっぽいものを作りました。

勉強中に線形探索アロケータはメモリが断片化しやすいからやめとけ的な記事も見つけたので、本当はTLSFアロケータなどの有名な探索アルゴリズムを使った方がいいのかもしれません・・・

自分のゲームプログラムは基本的に後入れ先出し方式なので今回作成したアロケータではメモリの断片化が発生しにくいと考えてます。

ただ今後マップにオブジェクトがスポーンしたりするゲーム作るってなると考えを改めないといけないです(汗



今回はstack_tagged_blocksアロケータと命名しています。



アロケータで使用する値クラス

staticな変数を格納してあるクラスです。本体がテンプレートクラスなので継承して使います。

stack_tagged_blocks_value

 # start_ptr : void* = nullptr

 # total_size : std::uint32_t = 0

 # alignment : std::uint32_t = 1

 


アロケータ本体

値クラスをprivate継承で隠蔽しています。

(23/04/08追記 alignmentをtemplateパラメータにしていましたが、値クラスに移動しました。alignment設定用のspecify_alignment関数も追加しています。)

template<typename T>

stack_tagged_blocks

 

 + initialize( const std::uint32_t& total_size ) : void

 + finalize() : void

 + specify_alignment(const std::uint32_t& alignment) : void

 + allocate( const std::uint32_t& num ) : T*

 + deallocate( T* p, const std::uint32_t& num) : void



initialize・finalize関数

ここででっかくメモリを確保してプールします。ゲーム開始時に一度だけinitialize、ゲーム終了時に一度だけfinalizeするイメージです。

mallocとfreeはここでのみ行います。なのでmallocの成功チェック、free前にnullptrチェック、free後にnullptr代入などの対策も。mallocが失敗した場合はアプリケーションが成り立たないので例外(23/04/08追記 std::bad_alloc)をスローする仕様にしました。

(メモリが不足していますっていうメッセージボックスを出して終了っていうのが丸い気もしている。。。)

それと、initializeでは確保したメモリにタグを入れます。ヘッダに「未利用:1・利用:2・余白付き利用:3+余白サイズ」のフラグ、利用サイズを入れ、フッタにフッタアドレスからヘッダアドレスまでのサイズを入れておきます。

値クラスのstart_ptrとtotal_sizeもここで代入しておきます。

確保したメモリのイメージはこんな感じ
(23/04/19追記:こちらの記事でデリータに対応する構造に変更したため、この記事の構造は古くなっています。m(__)m)

←前[header_flag:4byte][header_size:4byte][padding:?byte][allocated_size:?byte][footer:4byte]後→

今回のアロケータの大きな欠点は1ブロック当たり12byteロスする設計になっていることです。


allocate関数

要求サイズが満たされているブロックを後ろから検索します。後ろから4byte戻したところにフッタがあるので、そこからヘッダのフラグを読みに行きます。

要求サイズが満たされている場合、分割してタグを入れるスペースがあるなら前側のブロックを返せるように分割します。分割したブロックには新たにタグをつけたり既存のタグを修正しておきます。

分割してタグを入れるスペースがないなら大きめのサイズで返します。

メモリアドレスの位置を指定した数値の倍数にすること(アラインメント)を要求されている場合はヘッダと確保メモリの間に余白(パディング)を入れますが、大きめにサイズ確保している場合は大きめ部分を余白に回す計算を行います。


deallocate関数

確保メモリのヘッダフラグを「未利用」に戻します。その時に前後のブロックを確認して未利用ならマージします。





アロケータ初心者なのでかなり手間取ってしまいましたが、速度を計測してみたところ速くなってました!やったー!下に計測結果を置いておきます~



計測したのはReleaseビルドでchar型8バイト分を確保・解放を10万回行ったときの時間で、単位はナノ秒(10億分の1秒)です。

1回目
自作allocate deallocate: 885500
malloc free: 3549200
new delete: 4475700


2回目
自作allocate deallocate: 757700
malloc free: 3793800
new delete: 3999400


3回目
自作allocate deallocate: 735400
malloc free: 3492100
new delete: 3717700



Debugビルドでは死ぬほどデバッグログを吐かせているので遅いです!!!!!うわあぁあぁ(発狂



ということで、今週からようやくゲーム制作っぽい雰囲気に戻れそうです。スケジュール厳しいのでLive2D関係のプログラムはしばらく後回しにしますが、空いた時間にちょこちょこ進めていけたらと思います。

■今週のゲーム制作:明日、初めて彼女と❤の機能を移植

今週も頑張りますぞ~!