メモリ管理

概要

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

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

It is important to understand that the management of the Python heap is performed by the interpreter itself and that the user has no control over it, even if she regularly manipulates object pointers to memory blocks inside that heap. The allocation of heap space for Python objects and other internal buffers is performed on demand by the Python memory manager through the Python/C API functions listed in this document.

メモリ管理の崩壊を避けるため、拡張モジュールの作者は決して 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 メモリマネージャの管理から完全に外れることに注意してください。

参考

環境変数 PYTHONMALLOC を使用して Python が利用するメモリアロケータを制御することができます。

環境変数 PYTHONMALLOCSTATS を使用して、新たなオブジェクトアリーナが生成される時と、シャットダウン時に pymalloc メモリアロケータ の統計情報を表示できます。

生メモリインタフェース

以下の関数群はシステムのアロケータをラップします。 これらの関数はスレッドセーフで、 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)

Frees the memory block pointed to by p, which must have been returned by a previous call to PyMem_RawMalloc(), PyMem_RawRealloc() or PyMem_RawCalloc(). Otherwise, or if PyMem_RawFree(p) has been called before, undefined behavior occurs.

pNULL の場合何もしません。

メモリインタフェース

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

デフォルトではこれらの関数は pymalloc メモリアロケータ を利用します。

警告

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

バージョン 3.6 で変更: デフォルトのアロケータがシステムの malloc() から pymalloc になりました。

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)

Object allocators

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

デフォルトではこれらの関数は pymalloc メモリアロケータ を利用します。

警告

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

void* PyObject_Malloc(size_t n)

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

Requesting zero bytes returns a distinct non-NULL pointer if possible, as if PyObject_Malloc(1) had been called instead. The memory will not have been initialized in any way.

void* PyObject_Calloc(size_t nelem, size_t elsize)

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

Requesting zero elements or elements of size zero bytes returns a distinct non-NULL pointer if possible, as if PyObject_Calloc(1, 1) had been called instead.

バージョン 3.5 で追加.

void* PyObject_Realloc(void *p, size_t n)

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

If p is NULL, the call is equivalent to PyObject_Malloc(n); else if n is equal to zero, the memory block is resized but is not freed, and the returned pointer is non-NULL.

Unless p is NULL, it must have been returned by a previous call to PyObject_Malloc(), PyObject_Realloc() or PyObject_Calloc().

If the request fails, PyObject_Realloc() returns NULL and p remains a valid pointer to the previous memory area.

void PyObject_Free(void *p)

Frees the memory block pointed to by p, which must have been returned by a previous call to PyObject_Malloc(), PyObject_Realloc() or PyObject_Calloc(). Otherwise, or if PyObject_Free(p) has been called before, undefined behavior occurs.

pNULL の場合何もしません。

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

バージョン 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

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

PYMEM_DOMAIN_RAW

関数:

PYMEM_DOMAIN_MEM

関数:

PYMEM_DOMAIN_OBJ

関数:

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() を呼びだした。
  • バッファの開始前の書き込み (バッファアンダーフロー) を検出します
  • バッファ終了後の書き込み (バッファオーバーフロー) を検出します
  • Check that the GIL is held when allocator functions of PYMEM_DOMAIN_OBJ (ex: PyObject_Malloc()) and PYMEM_DOMAIN_MEM (ex: PyMem_Malloc()) domains are called

On error, the debug hooks use the tracemalloc module to get the traceback where a memory block was allocated. The traceback is only displayed if tracemalloc is tracing Python memory allocations and the memory block was traced.

These hooks are installed by default if Python is compiled in debug mode. The PYTHONMALLOC environment variable can be used to install debug hooks on a Python compiled in release mode.

バージョン 3.6 で変更: This function now also works on Python compiled in release mode. On error, the debug hooks now use tracemalloc to get the traceback where a memory block was allocated. The debug hooks now also check if the GIL is held when functions of PYMEM_DOMAIN_OBJ and PYMEM_DOMAIN_MEM domains are called.

pymalloc アロケータ

Python には、寿命の短いの小さな(512バイト以下の)オブジェクトに最適化された pymalloc アロケータがあります。 pymalloc は、256 KBの固定サイズの "アリーナ" と呼びれるメモリマッピングを使います。512バイトよりも大きな割り当てでは、 PyMem_RawMalloc()PyMem_RawRealloc() にフォールバックします。

pymalloc is the default allocator of the PYMEM_DOMAIN_MEM (ex: PyMem_Malloc()) and PYMEM_DOMAIN_OBJ (ex: PyObject_Malloc()) domains.

アリーナアロケータは、次の関数を使います:

  • Windows では VirtualAlloc()VirtualFree()
  • 利用できる場合、mmap()munmap()
  • それ以外の場合は malloc()free()

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

バージョン 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 による新しいオブジェクト型の定義や実装に関する記述の中で説明します。

関連キーワード:  PyMem, メモリ, 関数, size, PyObject, バイト, 操作, Malloc, ポインタ, 確保