Memory Context †
- フラットなメモリ空間を構造化して管理するための機能であり、backendで用いられる。
- メモリコンテキストの初期化時、TopMemoryContextとErrorContextが作成される。この他、トップレベルのメモリコンテキストには、PostmasterContext、CacheMemoryContext、MessageContext、TopTransactionContext、CurTransactionContextがあり、TopMemoryContextは、全てのメモリコンテキストの親である。
参考 - メモリコンテキストはツリー構造で管理され、ノードは1つの親ノードを持てる。
あるコンテキストを削除することで、そのコンテキストの子である一連のメモリコンテキストを削除したりすることができる。 - メモリコンテキストはそれぞれ独立したメモリ領域を持ち、親子の関連はメモリ使用域とは関係ない。
Memory Contextの構造と操作 †
Memory Contextを使用することでメモリ管理が容易になり、メモリリークなどのバグを減らせる。
基本的なメモリコンテキストの操作イメージは以下の図の通りである。
Memory Contextに関連する構造体 †
MemoryContextData †
typedef struct MemoryContextData { NodeTag type; /* identifies exact kind of context */ /* these two fields are placed here to minimize alignment wastage: */ bool isReset; /* T = no space alloced since last reset */ bool allowInCritSection; /* allow palloc in critical section */ const MemoryContextMethods *methods; /* virtual function table */ MemoryContext parent; /* NULL if no parent (toplevel context) */ MemoryContext firstchild; /* head of linked list of children */ MemoryContext prevchild; /* previous child of same parent */ MemoryContext nextchild; /* next child of same parent */ const char *name; /* context name (just for debugging) */ const char *ident; /* context ID if any (just for debugging) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData;
参考struct MemoryContextData - https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/tags/REL_12_1
- メモリコンテキストは、parent(親)、childlen(子ノード)、prevchild(同階層の前のノード)、nextchild(同階層の次のノード)へのポインタを持っている。
- メモリアロケーションの操作は、struct MemoryContextMethodsの関数群が担う。
MemoryContextMethods †
メモリコンテキストの具象実装である。
typedef struct MemoryContextMethods { void *(*alloc) (MemoryContext context, Size size); /* call this free_p in case someone #define's free() */ void (*free_p) (MemoryContext context, void *pointer); void *(*realloc) (MemoryContext context, void *pointer, Size size); void (*reset) (MemoryContext context); void (*delete_context) (MemoryContext context); Size (*get_chunk_space) (MemoryContext context, void *pointer); bool (*is_empty) (MemoryContext context); void (*stats) (MemoryContext context, MemoryStatsPrintFunc printfunc, void *passthru, MemoryContextCounters *totals); #ifdef MEMORY_CONTEXT_CHECKING void (*check) (MemoryContext context); #endif } MemoryContextMethods;
参考struct MemoryContextMethods - https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/tags/REL_12_1
例えば、デフォルトで使われるAllocSetは以下の関数で構成される。
static const MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, AllocSetIsEmpty, AllocSetStats #ifdef MEMORY_CONTEXT_CHECKING ,AllocSetCheck #endif };
参考MemoryContextMethods AllocSetMethods - https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/tags/REL_12_1
AllocSetContext †
最も標準的なMemoryContextの実装はAllocSetである。
以下はAllocSetの構造外観を図示したものである。
(詳細は、aset.cを参照して欲しい)
参考 src/backend/utils/mmgr/aset.c - https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/tags/REL_12_1
- メモリ領域をブロック(block: AllocBlockData)とチャンク(chunk: AllocChunkData)という概念で管理する。
ブロックは、1つまたは複数のチャンクから構成される。チャンクは、pallocごとに実際に割り当てされるメモリ領域であり、pallocで返されるメモリ番地はチャンクのデータ部の先頭アドレスである。 - ブロックのfreeptrは、ブロック内の次に割り当てるメモリ領域の先頭を指し、endptrはブロックの末尾を指す。
- allocChunkLimitを超えるサイズ要求があった場合は、新規にブロックを作成し丸々割り当てる。新たに作成したブロックは、AllocSetのブロックリストの先頭ブロック(アクティブなブロック)の次に挿入される。allocChunkLimitは、maxBlockSizeブロックサイズにより決定され、デフォルトはALLOC_CHUNK_LIMIT(8K)である。
- メモリ割り当てで要求されたサイズにマッチしない且つブロック内に空きスペースが存在する場合、空きスペースはAllocSetのfreelistの管理下に置かれる。freelistは配列であり、0番目のインデックスの8bytes領域から2のべき乗でサイズアップし、10番目は8192bytesの領域を持つ。次に新たなメモリ割り当て要求があった際、本サイズを満たすチャンクがfreelistに存在する場合は、そのチャンクが使用される。
- ブロック内に要求されたサイズの空き領域が存在しない、freelistにも適切なサイズのチャンクが存在しない場合、新規にブロックが作成される。ブロックは、initBlockSizeの2のべき乗でサイズアップする。例えば、AllocSetの初期化時のブロック割り当てが1024bytesであった場合、次のブロック割り当てでは1024bytes、以降2048bytes、...とサイズアップする。これは、nextBlockSizeで決まる。ただし、サイズアップではmaxBlockSizeを超えることはない。
- 標準的なサイズ構成であるAllocSetContextは削除時にcontext_freelistsという配列に蓄えられ再利用される。ただし、MAX_FREE_CONTEXTS(デフォルト100)を超えるサイズのコンテキストが既に存在する場合、それらのコンテキストは一度捨てられてから削除対象のコンテキストがcontext_freelistsに加えられる。
ALLOCSET_DEFAULTは0、ALLOCSET_SMALLは1のインデックスである。 - pfreeをするとfreelistにチャンクが追加される。ただし、allocChunkLimitを超えるブロックの場合はブロックの領域が解放される。なお、freelistの要素はチャンクのリストでありLIFO(Last In, First Out, 後入れ先出し)で格納される。
AllocSetに関連する構造体 †
AllocSetContext †
AllocSetに関する情報を保持する。
typedef struct AllocSetContext { MemoryContextData header; /* Standard memory-context fields */ /* Info about storage allocated in this context: */ AllocBlock blocks; /* head of list of blocks in this set */ AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* free chunk lists */ /* Allocation parameters for this context: */ Size initBlockSize; /* initial block size */ Size maxBlockSize; /* maximum block size */ Size nextBlockSize; /* next block size to allocate */ Size allocChunkLimit; /* effective chunk size limit */ AllocBlock keeper; /* keep this block over resets */ /* freelist this context could be put in, or -1 if not a candidate: */ int freeListIndex; /* index in context_freelists[], or -1 */ } AllocSetContext;
- headerは、MemoryContextData構造体である。MemoryContextCreate関数で返されるコンテキストはAllocSetContextの構造を持つポインタである。
- keeperはAllocSet初期化時の初期ブロックである。
AllocBlockData †
typedef struct AllocBlockData { AllocSet aset; /* aset that owns this block */ AllocBlock prev; /* prev block in aset's blocks list, if any */ AllocBlock next; /* next block in aset's blocks list, if any */ char *freeptr; /* start of free space in this block */ char *endptr; /* end of space in this block */ } AllocBlockData;
AllocChunkData †
typedef struct AllocChunkData { /* size is always the size of the usable space in the chunk */ Size size; #ifdef MEMORY_CONTEXT_CHECKING /* when debugging memory usage, also store actual requested size */ /* this is zero in a free chunk */ Size requested_size; #define ALLOCCHUNK_RAWSIZE (SIZEOF_SIZE_T * 2 + SIZEOF_VOID_P) #else #define ALLOCCHUNK_RAWSIZE (SIZEOF_SIZE_T + SIZEOF_VOID_P) #endif /* MEMORY_CONTEXT_CHECKING */ /* ensure proper alignment by adding padding if needed */ #if (ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF) != 0 char padding[MAXIMUM_ALIGNOF - ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF]; #endif /* aset is the owning aset if allocated, or the freelist link if free */ void *aset; /* there must not be any padding to reach a MAXALIGN boundary here! */ } AllocChunkData;
参考 src/backend/utils/mmgr/aset.c - https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/tags/REL_12_1
MemoryContextの情報 †
MemoryContextStats関数を呼ぶことで、メモリコンテキストの内部情報を知ることができる。
- MemoryContextStats() - https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/tags/REL_12_1
AllocSet †
AllocSetの場合は、MemoryContextStats関数の呼び出しでAllocSetStats関数が呼ばれる。
出力される情報は以下の通りである。
1364 snprintf(stats_string, sizeof(stats_string), 1365 "%zu total in %zd blocks; %zu free (%zd chunks); %zu used", 1366 totalspace, nblocks, freespace, freechunks, 1367 totalspace - freespace);
参考 AllocSetStats() - https://git.postgresql.org/gitweb/?p=postgresql.git;a=shortlog;h=refs/tags/REL_12_1
項目 | 変数 | 説明 |
---|---|---|
total | totalspace | ブロックサイズの合計(AllocSetContextのサイズも含む)(bytes) |
blocks | nblocks | ブロック数(個) |
free | freespace | ブロック内の空き領域の合計(フリーリスト、チャンクヘッダサイズ含む)(bytes) |
chunks | freechunks | フリーリスト内のチャンク数(個)。サイズ混合の数である。 |
used | totalspace - freespace | 使用領域(bytes) |
使用領域は、上記の計算からわかるように、ブロックやチャンクのヘッダサイズも含んだ値である。
アライメントやfreelistのchunk割り当てなどがあるため、実際にpallocなどのアロケーション関数に指定した割り当てサイズの合計となるとは限らない。
参考までにPG12.1でのサイズを以下に掲載する。
(lldb) print sizeof(AllocChunkData) (unsigned long) $0 = 24 (lldb) print sizeof(AllocBlockData) (unsigned long) $1 = 40 (lldb) print sizeof(AllocSetContext) (unsigned long) $2 = 216
Memory Contextの使用例 †
PostgreSQL/開発/include/utils/palloc.h
以下で、サクッとMemoryContextStats()を実行して確認できる。
$ pgenv extension init my $ cd my $ make install $ psql -d postgres postgres=# CREATE EXTENSION myext; postgres=# select myext();
参考 https://github.com/moritetu/pgenv2.git
myext.c
Datum myext(PG_FUNCTION_ARGS) { MemoryContext mycontext; MemoryContext oldcxt; char *p; mycontext = AllocSetContextCreate(CurrentMemoryContext, "MyContext", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(mycontext); /* Begin MyContext */ MemoryContextStats(CurrentMemoryContext); p = palloc(32); MemoryContextStats(CurrentMemoryContext); MemoryContextSwitchTo(oldcxt); /* End MyContext */ MemoryContextDelete(mycontext); PG_RETURN_BOOL(true); }