メモリ管理

概要

Python におけるメモリ管理には、全ての Python オブジェクトとデータ構造が入ったプライベートヒープ (private heap) が必須です。プライベートヒープの管理は、内部的には Python メモリマネージャ (Python memory manager) が確実に行います。Python メモリマネージャには、共有 (sharing)、セグメント分割 (segmentation)、事前割り当て (preallocation)、キャッシュ化 (caching) といった、様々な動的記憶管理の側面を扱うために、個別のコンポーネントがあります。

最低水準層では、素のメモリ操作関数 (raw memory allocator) がオペレーティングシステムのメモリ管理機構とやりとりして、プライベートヒープ内にPython 関連の全てのデータを記憶するのに十分な空きがあるかどうか確認します。素のメモリ操作関数の上には、いくつかのオブジェクト固有のメモリ操作関数があります。これらは同じヒープを操作し、各オブジェクト型固有の事情に合ったメモリ管理ポリシを実装しています。例えば、整数オブジェクトは文字列やタプル、辞書とは違ったやり方でヒープ内で管理されます。というのも、整数には値を記憶する上で特別な要件があり、速度/容量のトレードオフが存在するからです。このように、Python メモリマネジャは作業のいくつかをオブジェクト固有のメモリ操作関数に委譲しますが、これらの関数がプライベートヒープからはみ出してメモリ管理を行わないようにしています。

重要なのは、たとえユーザがいつもヒープ内のメモリブロックを指すようなオブジェクトポインタを操作しているとしても、Python 用ヒープの管理はインタプリタ自体が行うもので、ユーザがそれを制御する余地はないと理解することです。Python オブジェクトや内部使用されるバッファを入れるためのヒープ空間のメモリ確保は、必要に応じて、Python メモリマネージャがこのドキュメント内で列挙しているPython/C API 関数群を介して行います。

メモリ管理の崩壊を避けるため、拡張モジュールの作者は決して Python オブジェクトを C ライブラリが公開している関数: malloc()calloc()realloc() および free() で操作しようとしてはなりません。こうした関数を使うと、C のメモリ操作関数と Python メモリマネージャとの間で関数呼び出しが交錯します。 C のメモリ操作関数とPython メモリマネージャは異なるアルゴリズムで実装されていて、異なるヒープを操作するため、呼び出しの交錯は致命的な結果を招きます。とはいえ、個別の目的のためなら、 C ライブラリのメモリ操作関数を使って安全にメモリを確保したり解放したりできます。例えば、以下がそのような例です:

PyObject *res;
char *buf = (char *) malloc(BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
...Do some I/O operation involving buf...
res = PyBytes_FromString(buf);
free(buf); /* malloc'ed */
return res;

この例では、I/O バッファに対するメモリ要求は C ライブラリのメモリ操作関数を使っています。Python メモリマネージャーは戻り値として返される文字列オブジェクトを確保する時にだけ必要です。

とはいえ、ほとんどの状況では、メモリの操作は Python ヒープに固定して行うよう勧めます。なぜなら、Python ヒープは Python メモリマネジャの管理下にあるからです。例えば、インタプリタを C で書かれた新たなオブジェクト型で拡張する際には、ヒープでのメモリ管理が必要です。Python ヒープを使った方がよいもう一つの理由として、拡張モジュールが必要としているメモリについて Python メモリマネージャに 情報を提供 してほしいということがあります。たとえ必要なメモリが内部的かつ非常に特化した用途に対して排他的に用いられるものだとしても、全てのメモリ操作要求を Python メモリマネージャに委譲すれば、インタプリタはより正確なメモリフットプリントの全体像を把握できます。その結果、特定の状況では、Python メモリマネージャがガベージコレクションやメモリのコンパクト化、その他何らかの予防措置といった、適切な動作をトリガできることがあります。上の例で示したように C ライブラリのメモリ操作関数を使うと、I/O バッファ用に確保したメモリは Python メモリマネージャの管理から完全に外れることに注意してください。

参考

PYTHONMALLOCSTATS 環境変数を使用して新たなオブジェクトアリーナが生成される度と、シャットダウン時にメモリ割り当ての統計を表示出来ます。

生メモリインタフェース

以下の関数群はシステムのアロケータをラップします。 これらの関数はスレッドセーフで、 GIL を保持していなくても呼び出すことができます。

デフォルトの生メモリブロックアロケーターは次の関数を利用します: malloc(), calloc(), realloc(), free() 0バイトを要求されたときには malloc(1) (あるいは calloc(1, 1)) を呼びます。

バージョン 3.4 で追加.

void* PyMem_RawMalloc(size_t n)

n バイトを割り当て、そのメモリを指す void* 型のポインタを返します。要求が失敗した場合 NULL を返します。

0バイトを要求すると、 PyMem_RawMalloc(1) が呼ばれたときと同じように、可能なら NULL でないユニークなポインタを返します。確保されたメモリーにはいかなる初期化も行われません。

void* PyMem_RawCalloc(size_t nelem, size_t elsize)

各要素が elsize バイトの要素 nelem 個分のメモリーを確保し、そのメモリーを指す void* 型のポインタを返します。アロケートに失敗した場合は NULL を返します。確保されたメモリー領域はゼロで初期化されます。

要素数か要素のサイズが0バイトの要求に対しては、可能なら PyMem_RawCalloc(1, 1) が呼ばれたのと同じように、ユニークな NULL でないポインタを返します。

バージョン 3.5 で追加.

void* PyMem_RawRealloc(void *p, size_t n)

p が指すメモリブロックを n バイトにリサイズします。古いサイズと新しいサイズの小さい方までの内容は変更されません。

pNULL の場合呼び出しは PyMem_RawMalloc(n) と等価です。そうでなく、 n がゼロに等しい場合、メモリブロックはリサイズされますが解放されません。返されたポインタは非 NULL です。

pNULL でない限り、p はそれより前の PyMem_RawMalloc(), PyMem_RawRealloc(), PyMem_RawCalloc() の呼び出しにより返されなければなりません。

要求が失敗した場合 PyMem_RawRealloc()NULL を返し、 p は前のメモリエリアをさす有効なポインタのままです。

void PyMem_RawFree(void *p)

p が指すメモリブロックを解放します。 p は以前呼び出した PyMem_RawMalloc(), PyMem_RawRealloc(), PyMem_RawCalloc() の返した値でなければなりません。それ以外の場合や PyMem_Free(p) を呼び出した後だった場合、未定義の動作になります。

pNULL の場合何もしません。

メモリインタフェース

以下の関数群が利用して Python ヒープに対してメモリを確保したり解放したり出来ます。これらの関数は ANSI C 標準に従ってモデル化されていますが、0 バイトを要求した際の動作についても定義しています:

デフォルトのメモリアロケータは以下の関数を使用します: malloc(), calloc(), realloc(), free()。ゼロバイトが要求された場合 malloc(1) (または calloc(1, 1)) を呼びます。

警告

これらの関数を呼ぶときには、 GIL を保持しておく必要があります。

void* PyMem_Malloc(size_t n)

n バイトを割り当て、そのメモリを指す void* 型のポインタを返します。要求が失敗した場合 NULL を返します。

0バイトを要求すると、 PyMem_Malloc(1) が呼ばれたときと同じように、可能なら NULL でないユニークなポインタを返します。 確保されたメモリーにはいかなる初期化も行われません。

void* PyMem_Calloc(size_t nelem, size_t elsize)

各要素が elsize バイトの要素 nelem 個分のメモリーを確保し、そのメモリーを指す void* 型のポインタを返します。アロケートに失敗した場合は NULL を返します。確保されたメモリー領域はゼロで初期化されます。

要素数か要素のサイズが0バイトの要求に対しては、可能なら PyMem_Calloc(1, 1) が呼ばれたのと同じように、ユニークな NULL でないポインタを返します。

バージョン 3.5 で追加.

void* PyMem_Realloc(void *p, size_t n)

p が指すメモリブロックを n バイトにリサイズします。古いサイズと新しいサイズの小さい方までの内容は変更されません。

pNULL の場合呼び出しは PyMem_Malloc(n) と等価です。そうでなく、 n がゼロに等しい場合、メモリブロックはリサイズされますが解放されません。返されたポインタは非 NULL です。

pNULL でない限り、p はそれより前の PyMem_Malloc(), PyMem_Realloc() または PyMem_Calloc() の呼び出しにより返されなければなりません。

要求が失敗した場合 PyMem_Realloc()NULL を返し、 p は前のメモリエリアをさす有効なポインタのままです。

void PyMem_Free(void *p)

p が指すメモリブロックを解放します。 p は以前呼び出した PyMem_Malloc()PyMem_Realloc()、または PyMem_Calloc() の返した値でなければなりません。それ以外の場合や PyMem_Free(p) を呼び出した後だった場合、未定義の動作になります。

pNULL の場合何もしません。

以下に挙げる型対象のマクロは利便性のために提供されているものです。TYPE は任意の C の型を表します。

TYPE* PyMem_New(TYPE, size_t n)

PyMem_Malloc() と同じですが、 (n * sizeof(TYPE)) バイトのメモリを確保します。 TYPE* に型キャストされたポインタを返します。メモリには何の初期化も行われていません。

TYPE* PyMem_Resize(void *p, TYPE, size_t n)

PyMem_Realloc() と同じですが、 (n * sizeof(TYPE)) バイトにサイズ変更されたメモリを確保します。 TYPE* に型キャストされたポインタを返します。 関数が終わったとき、 p は新しいメモリ領域のポインタか、失敗した場合は NULL になります。

これは C プリプロセッサマクロです。p は常に再代入されます。エラー処理時にメモリを失うのを避けるには p の元の値を保存してください。

void PyMem_Del(void *p)

PyMem_Free() と同じです。

上記に加えて、C API 関数を介することなく Python メモリ操作関数を直接呼び出すための以下のマクロセットが提供されています。ただし、これらのマクロは Python バージョン間でのバイナリ互換性を保てず、それゆえに拡張モジュールでは撤廃されているので注意してください。

  • PyMem_MALLOC(size)
  • PyMem_NEW(type, size)
  • PyMem_REALLOC(ptr, size)
  • PyMem_RESIZE(ptr, type, size)
  • PyMem_FREE(ptr)
  • PyMem_DEL(ptr)

メモリアロケータをカスタマイズする

バージョン 3.4 で追加.

PyMemAllocatorEx

メモリブロックアロケータを記述するための構造体です。4つのフィールドを持ちます:

フィールド

意味

void *ctx

第一引数として渡されるユーザコンテクスト

void* malloc(void *ctx, size_t size)

メモリブロックを割り当てます

void* calloc(void *ctx, size_t nelem, size_t elsize)

0で初期化されたメモリブロックを割り当てます

void* realloc(void *ctx, void *ptr, size_t new_size)

メモリブロックを割り当てるかリサイズします

void free(void *ctx, void *ptr)

メモリブロックを解放する

バージョン 3.5 で変更: PyMemAllocator 構造体が PyMemAllocatorEx にリネームされた上で calloc フィールドが追加されました。

PyMemAllocatorDomain

アロケータドメインを同定するための列挙型です。ドメインは:

void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

指定されたドメインのメモリブロックアロケータを取得します。

void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

指定されたドメインのメモリブロックアロケータを設定します。

新しいアロケータは、0バイトを要求されたときユニークな NULL でないポインタを返さなければなりません。

PYMEM_DOMAIN_RAW ドメインでは、アロケータはスレッドセーフでなければなりません: アロケータが呼び出されたとき GIL は保持されていません。

新しいアロケータがフックでない (1つ前のアロケータを呼び出さない) 場合、 PyMem_SetupDebugHooks() 関数を呼び出して、新しいアロケータの上にデバッグフックを再度設置しなければなりません。

void PyMem_SetupDebugHooks(void)

以下の Python メモリアロケータ関数のバグを検出するフックを設定します。

新たに割り当てられたメモリはバイト 0xCB で埋められ、解放されたメモリはバイト 0xDB で埋められます。追加のチェック:

  • API 違反を検出します。例えば PyMem_Malloc() が割り当てたバッファに PyObject_Free() を呼んだ場合

  • バッファの開始前の書き込み (バッファアンダーフロー) を検出します

  • バッファ終了後の書き込み (バッファオーバーフロー) を検出します

Python がデバッグモードでコンパイルされていない場合、この関数は何もしません。

PyObject アリーナアロケータのカスタマイズ

Python には512バイト未満のメモリ確保のための pymalloc があります。 このアロケータは寿命の短い小さなサイズのオブジェクトに最適化されています。 pymalloc は256 KBの固定サイズの “アリーナ” と呼ばれるメモリマッピングを使います。 512バイト以上のサイズのメモリを確保するときは、 PyMem_RawMalloc() および PyMem_RawRealloc() へ処理を差し戻します。

デフォルトのアリーナアロケータは次の関数を使います:

  • Windows では VirtualAlloc()VirtualFree()

  • 利用できる場合、mmap()munmap()

  • それ以外の場合は malloc()free()

バージョン 3.4 で追加.

PyObjectArenaAllocator

アリーナアロケータを記述するための構造体です。3つのフィールドを持ちます:

フィールド

意味

void *ctx

第一引数として渡されるユーザコンテクスト

void* alloc(void *ctx, size_t size)

size バイトのアリーナを割り当てます

void free(void *ctx, size_t size, void *ptr)

アリーナを解放します

PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)

アリーナアロケータを取得します。

PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)

アリーナアロケータを設定します。

使用例

最初に述べた関数セットを使って、 概要 節の例を Python ヒープに I/O バッファをメモリ確保するように書き換えたものを以下に示します:

PyObject *res;
char *buf = (char *) PyMem_Malloc(BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Do some I/O operation involving buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* allocated with PyMem_Malloc */
return res;

同じコードを型対象の関数セットで書いたものを以下に示します:

PyObject *res;
char *buf = PyMem_New(char, BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Do some I/O operation involving buf... */
res = PyBytes_FromString(buf);
PyMem_Del(buf); /* allocated with PyMem_New */
return res;

上の二つの例では、バッファを常に同じ関数セットに属する関数で操作していることに注意してください。実際、あるメモリブロックに対する操作は、異なるメモリ操作機構を混用する危険を減らすために、同じメモリ API ファミリを使って行うことが必要です。以下のコードには二つのエラーがあり、そのうちの一つには異なるヒープを操作する別のメモリ操作関数を混用しているので 致命的 (Fatal) とラベルづけをしています。

char *buf1 = PyMem_New(char, BUFSIZ);
char *buf2 = (char *) malloc(BUFSIZ);
char *buf3 = (char *) PyMem_Malloc(BUFSIZ);
...
PyMem_Del(buf3);  /* Wrong -- should be PyMem_Free() */
free(buf2);       /* Right -- allocated via malloc() */
free(buf1);       /* Fatal -- should be PyMem_Del()  */

素のメモリブロックを Python ヒープ上で操作する関数に加え、 PyObject_New()PyObject_NewVar() 、および PyObject_Del() を使うと、 Python におけるオブジェクトをメモリ確保したり解放したりできます。

これらの関数については、次章の C による新しいオブジェクト型の定義や実装に関する記述の中で説明します。