#author("2017-05-22T00:30:57+09:00","default:haikikyou","haikikyou")
#author("2017-05-22T00:31:06+09:00","default:haikikyou","haikikyou")
[[PostgreSQL/開発]]

#contents

* 概要 [#v016fcb6]

- メモリ操作のためのAPI
- メモリはチャンクと呼ばれる塊単位で扱われ,コンテキスト(メモリチャンクを管理)に紐づくようになっている。~
メモリはブロック単位で確保され,メモリチャンクはそのブロック領域から割り当てられる(詳細は,参考リンクの資料を参照)。
- コンテキストは木構造で管理されており,上位のコンテキストを破棄することで,下位のコンテキストの領域もまとめて解放することができる。

* 定義 [#de809d9f]

** マクロ [#j74669cf]

*** MemoryContextAllocExtendedのフラグ [#n24f4e6e]

|~マクロ名|~説明|h
|MCXT_ALLOC_HUGE|1GB以上のアロケーションを許容|
|MCXT_ALLOC_NO_OOM|OutOfMemoryでもエラーにしない|
|MCXT_ALLOC_ZERO|0クリアする|

*** palloc0fast [#nccac49d]

#geshi(c){{
// 引数1:アロケーションサイズ
palloc0fast(sz)
}}

- アライメントされていれば,アライメントチェックをスキップする。
- 実際には,MemoryContextAllocZeroAligned()とMemoryContextAllocZero()が呼び分けられる。

&size(12){&color(white,orange){ 参考 };}; [[palloc0fast >https://doxygen.postgresql.org/palloc_8h.html#a4e183544ac01993ec0eab3e721be413f]] - on doxygen.postgresql.org
** 列挙型 [#y26f0bb4]

** 構造体 [#r96eb7d4]

*** MemoryContextCallback [#e5b4cdd2]

|~番号|~データ型|~フィールド|~説明|h
|1|MemoryContextCallbackFunction|func|コールバック関数|
|2|void*|arg|コールバック関数に渡される引数|
|3|struct MemoryContextCallback|next|コールバック関数リストの次ノード。&br;コールバック関数はリスト構造で管理されており複数のコールバック関数を登録できる。|

*** MemoryContext [#o210b8c8]

MemoryContextDataポインタの別名

#geshi(c){{
typedef struct MemoryContextData *MemoryContext;
}}

&size(12){&color(white,orange){ 参考 };};  [[struct MemoryContextData >https://doxygen.postgresql.org/structMemoryContextData.html]] - on doxygen.postgresql.org
** 関数 [#f482c76f]

*** MemoryContextAlloc [#p8350333]

#geshi(c){{
// 引数1:アロケーション対象のコンテキスト
// 引数2:割当てサイズ(byte)
// 戻り値:メモリチャンクのポインタ
extern void *MemoryContextAlloc(MemoryContext context, Size size);
}}

- アロケーション処理は,AllocSetAlloc関数で行われる。
- 引数で指定されたメモリコンテキストに指定されたサイズのメモリ領域を確保する。
- チャンクリミットを超える場合は,新規にブロックが作成される。そうでない場合は,フリーリストから検索される。
- アクティブなアロケーションブロックにチャンクスペースがあれば,チャンクをブロックに確保しチャンクのポインタを返す。チャンクスペースがなければ,ブロックの残りの領域を整備しチャンクをフリーリストに置く。
- ブロックがなければ,新規にブロックを作成してチャンクのポインタを返す。
- マクロ定数MaxAllocSizeを超えるサイズはエラーになる。

&size(12){&color(white,orange){ 参考 };};~
- [[AllocSetAlloc()>https://doxygen.postgresql.org/aset_8c.html#a8b691370b6119226738db0605b5156bb]] - on doxygen.postgresql.org
- [[MemoryContextAlloc()>https://doxygen.postgresql.org/mcxt_8c.html#a7d104f43412595fb2d3ca12963084f23]] - on doxygen.postgresql.org

&size(12){&color(white,#00afcc){ サンプル };};
#geshi(c){{
void memory_sample()
{
    // 専用のコンテキストを作成
    MemoryContext context = AllocSetContextCreate(CurrentMemoryContext,
                                                   "mycontext",
                                                   0,
                                                   8 * 1024,
                                                   8 * 1024);
    MemoryContext oldcontext = NULL;

    // カレントコンテキストのスイッチ
    oldcontext = MemoryContextSwitchTo(context);

    // メモリチャンクが返される
    MyExtensionOptions *opt = (MyExtensionOptions*) MemoryContextAlloc(context,
                                                                       sizeof(MyExtensionOptions));
    // pallocは,内部でCurrentMemoryContextを使う
    // MyExtensionOptions *opt  = (MyExtensionOptions*)palloc(sizeof(MyExtensionOptions));
    opt->optcontext = ForeignTableRelationId;
    opt->optname = pstrdup("option1");

    MemoryContextSwitchTo(oldcontext);
    MemoryContextDelete(context);
}
}}
*** MemoryContextAllocZero [#cac2fcde]

#geshi(c){{
// 引数1:アロケーション対象のコンテキスト
// 引数2:アロケーションサイズ(byte)
// 戻り値:メモリチャンクのポインタ
extern void *MemoryContextAllocZero(MemoryContext context, Size size);
}}

- MemoryContextAlloc()と同様にメモリ領域をコンキストに確保する。
- 確保されたチャンクを返す前に0初期化される。
- アライメントされているかがチェックされ,memsetをするかどうかが判定される。

*** MemoryContextAllocZeroAligned [#t5fba3c0]

#geshi(c){{
// 引数1:アロケーション対象のコンテキスト
// 引数2:アロケーションサイズ(byte)
// 戻り値:メモリチャンクのポインタ
extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
}}

- MemoryContextAlloc()と同様にメモリ領域をコンキストに確保する。
- MemoryContextAllocZero()との違いは,アライメントのチェックがない点。

*** MemoryContextAllocExtended [#k2a6ed9e]

#geshi(c){{
// 引数1:アロケーション対象のコンテキスト
// 引数2:アロケーションサイズ(byte)
// 引数3:メモリアロケーション動作を決めるフラグ
// 戻り値:メモリチャンクのポインタ
extern void *MemoryContextAllocExtended(MemoryContext context, Size size, int flags);
}}

- 基本的には,MemoryContextAlloc()と同様にメモリ領域をコンキストに確保する。
- 違いは,フラグによって細かな動作を指定できる点である。

&size(12){&color(white,orange){ 参考 };};  [[MemoryContextAllocExtended()>https://doxygen.postgresql.org/palloc_8h.html#a7093367d96f76697f3a7e8e534b1115e]] - on doxygen.postgresql.org

&size(12){&color(white,#00afcc){ サンプル };};
#geshi(c){{
typedef struct MyExtensionOptions {
    const char *optname;
    Oid optcontext;
} MyExtensionOptions;

void memory_sample()
{
    MemoryContext context = AllocSetContextCreate(CurrentMemoryContext,
                                                   "mycontext",
                                                   0,
                                                   8 * 1024,
                                                   8 * 1024);
    MemoryContext oldcontext = NULL;

    oldcontext = MemoryContextSwitchTo(context);

    // 0クリア and メモリ確保失敗時にエラー報告
    MyExtensionOptions *opt = (MyExtensionOptions*) MemoryContextAllocExtended(context,
                                                                               sizeof(MyExtensionOptions),
                                                                               MCXT_ALLOC_ZERO | MCXT_ALLOC_NO_OOM);

    opt->optcontext = ForeignTableRelationId;
    opt->optname = pstrdup("option1");

    MemoryContextSwitchTo(oldcontext);
    MemoryContextDelete(context);
}
}}

*** palloc [#gad5f859]

#geshi(c){{
// 引数1:アロケーションサイズ(byte)
// 戻り値:メモリチャンクのポインタ
extern void *palloc(Size size);
}}

- MemoryContextAlloc()と同様,現在のコンテキスト(CurrentMemoryContext)にメモリ領域を確保する。

&size(12){&color(white,orange){ 参考 };}; [[palloc()>https://doxygen.postgresql.org/mcxt_8c.html#af25167ef63e16e1cbeb08b6f1f24e1d7]] - on doxygen.postgresql.org

&size(12){&color(white,#00afcc){ サンプル };};

#geshi(c){{
MyExtensionOptions *opt  = (MyExtensionOptions*)palloc(sizeof(MyExtensionOptions));
// 以下と同様である
// MyExtensionOptions  * opt = MemoryContextAlloc(CurrentMemoryContext, sizeof(MyExtensionOptions));
}}
*** palloc0 [#u63ebdcc]

#geshi(c){{
// 引数1:アロケーションサイズ(byte)
// 戻り値:メモリチャンクのポインタ
extern void *palloc0(Size size);
}}

- MemoryContextAllocZero()と同様,メモリ領域を確保し0クリアして返す。
*** palloc_extended [#g8ba4852]

#geshi(c){{
// 引数1:アロケーションサイズ(byte)
// 引数2:メモリアロケーション動作を決めるフラグ
// 戻り値:メモリチャンクのポインタ
extern void *palloc_extended(Size size, int flags);
}}

- MemoryContextAllocExtended()と同様,メモリ領域を確保し0クリアして返す。
- MemoryContextAllocExtended()は &size(12){&color(white,dimgray){ duplicate };}; となっている。
*** repalloc [#xec00f4d]

#geshi(c){{
// 引数1:メモリチャンクのポインタ
// 引数2:再割り当てするサイズ(byte)
// 戻り値:新しいメモリチャンクのポインタ
extern void *repalloc(void *pointer, Size size);
}}

- reallocの処理のメインは,AllocSetRealloc関数が行なう。
- 既存のメモリチャンクの領域を再割り当てする。
- 既存のメモリチャンクよりsizeが小さい場合は,そのメモリチャンクのrequested_sizeを更新してポインタを返す。
- 既存のメモリチャンクがメモリチャンクリミットより大きかった場合は,チャンクを拡張する。~
そうでない場合は,新しくメモリチャンクを確保し,既存のメモリチャンクは破棄する。

&size(12){&color(white,orange){ 参考 };};  [[AllocSetRealloc()>https://doxygen.postgresql.org/aset_8c.html#a8146fbbdd37c2e874c82dc8817c000a7]] - on doxygen.postgresql.org

&size(12){&color(white,#00afcc){ サンプル };};

#geshi(c){{
char *optname = palloc(1 << 4);
strcpy(optname, "hello world");
char *newoptname = repalloc(optname, 1 << 5);
strcpy(newoptname, "hello world, hello postgres");
}}
*** pfree [#ud59d33d]

#geshi(c){{
// 引数1:メモリチャンクのポインタ
extern void pfree(void *pointer);
}}

- 処理のメインはAllocSetFree関数で行われる。
- メモリチャンクを解放する。
- メモリチャンクリミットより大きかった場合は,メモリチャンクが属するブロックが解放される。~
そうでない場合は,フリーリストに置かれる。

&size(12){&color(white,orange){ 参考 };};  [[AllocSetFree()>https://doxygen.postgresql.org/aset_8c.html#af6b49f9c4ca02d769f4d52382ea1812d]] - on doxygen.postgresql.org
*** MemoryContextAllocHuge [#g6936e4a]

#geshi(c){{
// 引数1:アロケーション対象のコンテキスト
// 引数2:アロケーションサイズ(byte)
// 戻り値:メモリチャンクのポインタ
extern void *MemoryContextAllocHuge(MemoryContext context, Size size);
}}

- MemoryContextAlloc関数と同様だが,MemoryContextAllocよりも大きな上限sizeを指定できる。
- マクロ定数MaxAllocHugeSizeの値までsizeを指定することができる。~
MaxAllocHugeSizeは,「-1 >> 1」( SIZE_MAX / 2)となっている。
*** repalloc_huge [#hc5b705f]

#geshi(c){{
// 引数1:メモリチャンクのポインタ
// 引数2:再割り当てするサイズ(byte)
// 戻り値:新しいメモリチャンクのポインタ
extern void *repalloc_huge(void *pointer, Size size);
}}

- repalloc関数のHuge版。
*** MemoryContextSwitchTo [#t20cc811]

#geshi(c){{
// 引数1:スイッチするメモリコンテキスト
// 戻り値:古い(スイッチする前の)メモリコンテキスト
MemoryContext MemoryContextSwitchTo(MemoryContext context)
}}

- メモリコンテキストを引数で指定されたメモリコンテキストにスイッチする。

&size(12){&color(white,#00afcc){ サンプル };};
#geshi(c){{
void memory_sample()
{
    MemoryContext context = AllocSetContextCreate(CurrentMemoryContext,
                                                   "mycontext",
                                                   0,
                                                   8 * 1024,
                                                   8 * 1024);
    MemoryContext oldcontext = MemoryContextSwitchTo(context);

    StringInfo strinfo = makeStringInfo();
    appendStringInfo(strinfo, "%s", "hoge");
    elog(NOTICE, "%s", strinfo->data);
 
    MemoryContextSwitchTo(oldcontext);
    MemoryContextDelete(context);
}
}}

*** MemoryContextRegisterResetCallback [#v7316b3e]

#geshi(c){{
// 引数1:対象のメモリコンテキスト
// 引数2:reset/delete時に呼ばれるコールバック関数
extern void MemoryContextRegisterResetCallback(MemoryContext context, MemoryContextCallback *cb);
}}

- MemoryContextDelete関数やMemoryContextReset関数呼び出し時に実行されるコールバック関数をメモリコンテキストに登録する。~
コールバック関数はリスト構造となっており,複数のコールバック関数を登録できる。

&size(12){&color(white,orange){ 参考 };}; [[struct MemoryContextCallback>https://doxygen.postgresql.org/structMemoryContextCallback.html]] - on doxygen.postgresql.org

&size(12){&color(white,#00afcc){ サンプル };};

#geshi(c){{{
// callback function
void report_context_delete(void *arg)
{
    MemoryContext context = (MemoryContext)arg;
    elog(NOTICE, "%s will be deleted", context->name);
}

void memory_sample()
{
    MemoryContext context = AllocSetContextCreate(CurrentMemoryContext,
                                                   "mycontext",
                                                   0,
                                                   8 * 1024,
                                                   8 * 1024);
    // コールバック関数の宣言と初期化
    MemoryContextCallback callback = {
        report_context_delete, // Function Pointer
        context,               // Argument
    };

    // コールバック関数の登録
    MemoryContextRegisterResetCallback(context, &callback);

    MemoryContext oldcontext = MemoryContextSwitchTo(context);

    char *optname = palloc(16);
    strcpy(optname, "hello world");

    MemoryContextSwitchTo(oldcontext);

    // report_context_delete(context) is called
    MemoryContextDelete(context);
}
}}}
*** MemoryContextStrdup [#v1f2d1a6]

#geshi(c){{
// 引数1:対象のメモリコンキスト
// 引数2:複製するデータ
// 戻り値:複製されたデータのポインタ
extern char *MemoryContextStrdup(MemoryContext context, const char *string);
}}

- 引数で指定されたデータの複製を指定されたメモリコンテキストに割り当てる。~
具体的には,stringサイズを指定してMemoryContextAlloc関数を呼び,返されたメモリチャンクにデータstringを複製する。

&size(12){&color(white,orange){ 参考 };}; [[MemoryContextStrdup()>https://doxygen.postgresql.org/mcxt_8c.html#aa1463740fc0b6633db48785cdb607c99]] - on doxygen.postgresql.org
*** pstrdup [#hd88f9fa]

#geshi(c){{
// 引数1:複製するデータ
// 戻り値:複製されたデータのポインタ
extern char *pstrdup(const char *string);
}}

- MemoryContextStrdup関数と同様で,現在のメモリコンテキストに対して行われる。
*** pnstrdup [#yf1915a1]

#geshi(c){{
// 引数1:複製するデータ
// 引数2:複製するデータの長さ
// 戻り値:複製されたデータのポインタ
extern char *pnstrdup(const char *in, Size len);
}}

- pstrdup関数と同様だが,終端のヌルバイト ('\0') が追加される。

&size(12){&color(white,#00afcc){ サンプル };};

#geshi(c){{{
void sample() {
    const char *string = "hello world";
    size_t len = strlen(string);

    // pstrdup
    char *str = pstrdup(string);
    str[len] = '\0';
    elog(NOTICE, "str = %s", str);

    // pnstrdup
    char *str2 = pnstrdup(string, strlen(string));
    elog(NOTICE, "str2 = %s", str2);
}
}}}
*** psprintf [#h690aee1]

#geshi(c){{
// 引数:format書式定文字列,文字列
// 戻り値:フォーマットされた文字列のポインタ
extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2);
}}

- sprintfタイプの文字列フォーマットを行なう。
- 格納領域に必要なサイズは内部で計算される。初期サイズは,128byteで計算される。

&size(12){&color(white,orange){ 参考 };}; [[psprintf()>https://doxygen.postgresql.org/psprintf_8c.html#ac6058cd91fe6eda6a88b9729882759ef]] - on doxygen.postgresql.org

&size(12){&color(white,#00afcc){ サンプル };};

#geshi(c){{{
char *formatted_str = psprintf("Hello %s", "Mike");
elog(NOTICE, "%s", formatted_str); // Hello Mike
}}}
*** pvsnprintf [#e368904c]

#geshi(c){{
// 引数1:データを格納する領域のポインタ
// 引数2:書き込みされる最大サイズ
// 引数3:フォーマット書式指定文字列
// 引数4:書き込みするデータ
// 戻り値:書き込み長さ(nprinted)が,
//   ・len(NULL終端含む)より小さい場合は,nprinted
//   ・lenより大きい場合且つMaxAllocSize -2以下の場合は,nprinted+2
//   ・lenがMaxAllocSize / 2以上の場合は,MaxAllocSize
//   ・それ以外は,nprinted * 2
extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0);
}}

- vsnprintfのラッパーでC99非対応環境を考慮した実装。
- エラー発生時(nprintedが負数)は,elog(ERROR)となる(FRONTENDはexit())。

&size(12){&color(white,orange){ 参考 };}; [[pvsnprintf()>https://doxygen.postgresql.org/psprintf_8c.html#a32d42b419b5b40cbbdc035adf72afb1a]] - on doxygen.postgresql.org
* サンプルプログラム [#ecfe0973]

#geshi(c){{
// オプション用の構造体
typedef struct MyExtensionOptions {
    const char *optname;
    Oid optcontext;
} MyExtensionOptions;

// delete時に呼ばれるコールバック関数
void report_context_delete(void *arg)
{
    MemoryContext context = (MemoryContext)arg;
    elog(NOTICE, "%s will be deleted", context->name);
}

// サンプル
void memory_sample2()
{
    MemoryContext oldcontext = NULL;
    // 独自のコンテキスト作成
    MemoryContext context = AllocSetContextCreate(CurrentMemoryContext,
                                                  "mycontext",
                                                  0,
                                                  8 * 1024,
                                                  8 * 1024);

    // delete時のコールバック関数を登録
    MemoryContextCallback callback = {
        report_context_delete,
        context
    };
    MemoryContextRegisterResetCallback(context, &callback);

    // カレントコンテキストを作成したメモリコンテキストに切り替え
    oldcontext = MemoryContextSwitchTo(context);

    // カレントコンテキストで色々と操作
    StringInfo strinfo = makeStringInfo();
    appendStringInfo(strinfo, "%s", "hoge");

    MyExtensionOptions *opt  = (MyExtensionOptions*)palloc(sizeof(MyExtensionOptions));
    opt->optcontext = ForeignTableRelationId;
    opt->optname = psprintf("format_%s", "csv");

    // カレントコンテキストを切り替え前に戻す
    MemoryContextSwitchTo(oldcontext);

    // 作成したメモリコンテキストを破棄する
    MemoryContextDelete(context);
}
}}
* 参考・関連 [#rd6d9167]

- [[palloc.h>https://doxygen.postgresql.org/palloc_8h.html]] - on doxygen.postgresql.org
- [[memnodes.h>https://doxygen.postgresql.org/memnodes_8h.html]] - on doxygen.postgresql.org
- [[mcxt.c>https://doxygen.postgresql.org/mcxt_8c.html]] - on doxygen.postgresql.org
- [[aset.c>https://doxygen.postgresql.org/aset_8c.html]] - on doxygen.postgresql.org
- [[NTTデータ 井久保さんによる解析資料 ~ メモリ管理 ~>http://ikubo.x0.com/PostgreSQL/pdf/IK05_Memory_040702.pdf]] - http://ikubo.x0.com/PostgreSQL/pg_source.htm

&size(12){&color(white,orange){ 関連 };};

#related
* コメント [#t884c88a]

#comment

PR

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP