MGLのデフォルトアロケータ#

MGLにはカスタマイズ可能なメモリアロケータがの仕組みが用意されています。このアロケータはSTLエイリアスを経由することでアプリケーションからも利用可能です。

MGLは独自のメモリアロケータを作成して適用することも可能です。一方、デフォルトのアロケータもある程度のカスタマイズが可能となっています。本項では、標準構成に含まれるデフォルトアロケータの仕様やカスタマイズ方法についてを解説します。

デフォルトアロケータを使用せず、独自に作成したメモリアロケータを適用する場合はメモリアロケータの作成(準備中)を参照してください。

デフォルトアロケータの構成#

MGLのデフォルトアロケータは、固定サイズアロケータとフリーサイズのアロケータを併用した構成となっています。ここでは、この2つのアロケータについて解説します。

固定サイズアロケータ#

固定サイズアロケータは、厳しいサイズ制限と引き換えに高速に動作するアロケータです。

その名前が示す通り、このアロケータは固定サイズのメモリブロックを一定数保持しています。アロケーションによって未使用のブロックを1つ消費し、それが返却された際には未使用リストへと戻します。

この仕組みの単純性により、動作が高速であり断片化も発生しません。反面、メモリブロックより大きなサイズのアロケートは行えず、アロケート可能な数にも制限があります。また、ほとんどの場合、未使用の無駄な領域が発生してしまいます。

これらの性質により、固定サイズアロケータは小さなメモリリソースを頻繁に確保・解放する場合に最適です。これは、C++のSTLコンテナなどとは相性の良い性質です。多くの場合、STLコンテナはクラスオブジェクトのインスタンスのためにメモリリソースを要求しており、それは数バイトから数十バイトに収まる場合がほとんどであるためです。

MGLのデフォルトアロケータは4種類の固定サイズアロケータを保持しており、要求サイズに対して最適なアロケータを選択して使用します。

MGLの固定サイズアロケータが保持するメモリブロックのサイズは次の通りです。この値は変更可能であり、その方法はコンフィギュレーションにて後述します。

ブロックサイズ(Byte)

ブロック数

128

8192

256

4096

512

2048

1024

1024

要求されたサイズを満たすメモリブロックが存在しなかった場合、次に説明するフリーサイズのアロケータへと処理を移します。

フリーサイズのアロケータ#

フリーサイズのアロケータは、ごく一般的なメモリアロケータです。固定サイズアロケータでは扱えないサイズを要求された場合、こちらのアロケータを使用します。

デフォルトではC言語の標準関数であるmalloc() とfree() を使用します。使用する関数は変更可能で、アプリケーション側で用意した任意のメモリアロケータも使用できます。詳細はコンフィギュレーションを参照してください。

コンフィギュレーション#

デフォルトアロケータの設定を変更して使用するには、MGLの初期化前にMGL::Memory::SetDefaultAllocator使用します。この関数はMGL::Initialize(またはMGL::Win32::Main)よりも前に呼び出さなければなりません。また、MGLの仕様上、一度設定したアロケータを後から変更することはできません。

この関数の利用例を次に示します。

デフォルトアロケータのカスタマイズの例
MGL::Memory::DefaultAllocator::Configuration config;

// 固定サイズアロケータの最小ブロックサイズ
// この値は2のn乗の値でなければならない
config.fixedSizeAllocatorMinimumBlockSize = 128;

// 固定サイズアロケータのメモリプールのサイズ
// この値は最低でも最小ブロックサイズの8倍は必要
config.fixedSizeAllocatorMemoryPoolSize = 1 * 1024 * 1024;

// アロケータの指定
// size_t 型の引数を受け、void * 型の戻り値を返す関数を指定する
//   void *(*allocator)(size_t size);
config.allocator = malloc;

// デアロケータの指定
// void * 型の引数を受け、戻り値の型が void である関数を指定する
//   void (*deallocator)(void *buffer);
config.deallocator = free;

// デフォルトアロケータを設定
if (!MGL::Memory::SetDefaultAllocator(config))
{
    // 初期化に失敗した場合はここに到達
}

最初に宣言しているMGL::Memory::DefaultAllocator::Configurationは、デフォルトアロケータの設定パラメータを格納する構造体です。この構造体の各メンバにパラメータを格納し、MGL::Memory::SetDefaultAllocatorの引数に渡すことで設定を適用します。

設定可能なパラメータは次の通りです。

fixedSizeAllocatorMinimumBlockSize

固定サイズアロケータの最小ブロックサイズをバイト単位で指定します。この値は2のn乗である必要があります。

固定サイズアロケータのブロックサイズは、最小ブロックサイズを累乗した4段階のサイズで構成されます。例えば、最小ブロックサイズが128バイトの場合、128、256、512、1024の4つのブロックサイズが使用されます。

このパラメータを省略した場合は128が指定されます。

fixedSizeAllocatorMemoryPoolSize

固定サイズアロケータのメモリプールのサイズをバイト単位で指定します。この値は最低でも最小ブロックサイズの8倍以上が必要です。

各サイズのメモリブロックは、ここで指定したサイズをブロックサイズで分割して構築します。例えば、メモリプールに1MiBを指定した場合、128バイトのブロックの分割数は1MiB / 128 = 8192となります。固定サイズアロケータは4種類生成されるため、メモリプールの合計サイズはここで設定した値の4倍となります。

このパラメータを省略した場合は1 * 1024 * 1024 (1MiB) が指定されます。

allocator

フリーサイズのメモリリソースのアロケートに使用する関数を指定します。

指定する関数は、C言語の標準関数のmalloc() と同じフォーマットである必要があります。関数はマルチスレッドセーフでなければなりません。その他の仕様もmalloc() に準じていることを推奨します。

このパラメータを省略した場合、またはnullptrを指定した場合、C言語の標準関数のmalloc() が使用されます。

deallocator

フリーサイズのメモリリソースのデアロケートに使用する関数を指定します。

指定する関数は、C言語の標準関数のfree() と同じフォーマットである必要があります。関数はマルチスレッドセーフでなければなりません。その他の仕様もfree() に準じていることを推奨します。

このパラメータを省略した場合、またはnullptrを指定した場合、C言語の標準関数のfree() が使用されます。

アロケータの使用状況の取得#

MGLのアロケータには、メモリの使用状況をモニタリングするための仕組みが用意されています。

使用中のメモリアロケータから情報を取得するにはMGL::Memory::GetSizeInfoを使用します。この関数は特定のメモリアロケータに限定していないため、引数にはメモリアロケータ側で定義したキーを指定して対応した情報を取得します。また、キーと同時に引数argの指定も可能で、その意味は使用するキーによって異なります。

デフォルトアロケータでは、MGL::Hash::FNV1aで特定の文字列をハッシュ化した値をキーとして使用します。デフォルトアロケータで指定可能なキーは次の通りです。

MGL::Hash::FNV1a("FixedBlockCount")

固定サイズアロケータの数を取得します。現時点ではこのキーが返す値は4で固定されています。

MGL::Hash::FNV1a("FixedBlockSize")

固定サイズアロケータのブロックサイズを取得します。引数argには、0から始まる固定サイズアロケータのインデックスを指定します。

MGL::Hash::FNV1a("FixedBlockCapacity")

固定サイズアロケータのメモリブロックの総数を取得します。引数argには、0から始まる固定サイズアロケータのインデックスを指定します。

MGL::Hash::FNV1a("FixedBlockUsedCount")

固定サイズアロケータの使用中のメモリブロックの数を取得します。引数argには、0から始まる固定サイズアロケータのインデックスを指定します。

MGL::Hash::FNV1a("FixedBlockFreeCount")

固定サイズアロケータの未使用のメモリブロックの数を取得します。引数argには、0から始まる固定サイズアロケータのインデックスを指定します。

MGL::Hash::FNV1a("SystemUsedCount")

フリーサイズのアロケータが確保しているメモリリソースの数を取得します。この情報はプリプロセッサとしてMGL_DEBUGが定義されていない状況では取得できないため、リリースビルドでは取得に失敗します。

MGL::Hash::FNV1a("SystemUsedSize")

フリーサイズのアロケータが確保しているメモリリソースの合計サイズを取得します。この情報はプリプロセッサとしてMGL_DEBUGが定義されていない状況では取得できないため、リリースビルドでは取得に失敗します。

これらのキーを利用した、デフォルトアロケータの使用状況をトレース表示する例を次に示します。

デフォルトアロケータの使用状況をトレース表示する例
MGL_TRACE("--MEMORY MONITOR--");

// 固定サイズアロケータの使用状況を取得して表示
size_t fixedBlockCount = 0;
if (MGL::Memory::GetSizeInfo(fixedBlockCount, MGL::Hash::FNV1a("FixedBlockCount")))
{
    for (uint32_t i = 0; i < static_cast<uint32_t>(fixedBlockCount); i++)
    {
        size_t blockSize = 0;
        size_t usedCount = 0;
        size_t capacity = 0;

        // ブロックサイズ、使用中のブロックの数、ブロックの最大数をそれぞれ取得
        MGL::Memory::GetSizeInfo(blockSize, MGL::Hash::FNV1a("FixedBlockSize"), i);
        MGL::Memory::GetSizeInfo(usedCount, MGL::Hash::FNV1a("FixedBlockUsedCount"), i);
        MGL::Memory::GetSizeInfo(capacity,  MGL::Hash::FNV1a("FixedBlockCapacity"), i);

        // 整形して表示
        MGL_TRACE(MGL::Text::Format(
                    "{>4} {>4}/{>4}", {blockSize, usedCount, capacity}).c_str());
    }
}

// フリーサイズのアロケータの情報を取得して表示
size_t systemUsedCount = 0;
if (MGL::Memory::GetSizeInfo(systemUsedCount, MGL::Hash::FNV1a("SystemUsedCount")))
{
    size_t systemUsedSize = 0;
    if (MGL::Memory::GetSizeInfo(systemUsedSize, MGL::Hash::FNV1a("SystemUsedSize")))
    {
        MGL_TRACE(MGL::Text::Format(
                    " SYS {>4}:{,}", {systemUsedCount, systemUsedSize}).c_str());
    }
}
実行結果
--MEMORY MONITOR--
 128   63/8192
 256   12/4096
 512    3/2048
1024    1/1024
 SYS    2:15,648

注釈

実行結果は一例であり、実行環境によって異なります。