11. メモリ アロケータ
#11. メモリアロケータ
CPython はメモリを常に割り当てます。すべての整数オブジェクト、リスト オブジェクト、フレーム、タプル、辞書エントリ配列、文字列バッファ、コード オブジェクト、例外、モジュール、関数にはメモリが必要です。アロケータ システムは、これらの割り当てを高速かつ構造化し、デバッグ可能にし、プラットフォーム間で移植できるようにするために存在します。
CPython はアロケーターを 1 つだけ使用するわけではありません。複数のアロケーター ドメインとレイヤーを使用します。小さな Python オブジェクトは、CPython の特殊な小さなオブジェクト アロケーターを通過することがよくありますが、より大きなバッファーはプラットフォーム アロケーターを通過する場合があります。
11.1 CPython が独自のアロケータを持つ理由
Python プログラムは、有効期間の短いオブジェクトを多数作成します。python for i in range(1_000_000): x = (i, i + 1) このループは、多くのタプル オブジェクトと整数参照を割り当てます。すべての小さなオブジェクトの割り当てがシステムに直接行われる場合malloc、オーバーヘッドが高くなります。
CPython のアロケーター システムは、次のようにしてこれを改善します。```text serving small object allocations quickly grouping small allocations into arenas and pools reducing calls into the platform allocator supporting debug hooks separating allocator domains making object allocation behavior predictable enough for internals work
## 11.2 アロケータドメイン
CPython はメモリ割り当てをドメインに分割します。
重要なドメインは次のとおりです。
|ドメイン |一般的な使用法 |
| ------------- | --------------------------------------------------- |
|生のメモリ | Python オブジェクトの状態に依存しない低レベルのメモリ |
|メモリ |汎用 Python ランタイム メモリ |
|オブジェクトメモリ | Python オブジェクトの割り当て |
C API レベルでは、これらは関数のファミリーとして表示されます。```c
PyMem_RawMalloc
PyMem_RawCalloc
PyMem_RawRealloc
PyMem_RawFree
PyMem_Malloc
PyMem_Calloc
PyMem_Realloc
PyMem_Free
PyObject_Malloc
PyObject_Calloc
PyObject_Realloc
PyObject_Free
```各ドメインには異なるフック、制約、およびデバッグ動作がある可能性があるため、この区別は重要です。
簡単なルール:```text
PyMem_Raw*
use for memory that may be allocated without an initialized Python runtime
PyMem_*
use for general Python memory
PyObject_*
use for memory belonging to Python objects
```拡張コードは、同じファミリーの割り当て機能と解放機能と一致する必要があります。
正しい:```c
char *p = PyMem_Malloc(128);
if (p == NULL) {
return PyErr_NoMemory();
}
/* use p */
PyMem_Free(p);
```正しくない:```c
char *p = PyMem_Malloc(128);
free(p); /* wrong allocator family */
```アロケーター ファミリを混在させるとメモリが破損する可能性があります。
## 11.3 オブジェクトの割り当てとオブジェクトの初期化
割り当てはメモリを予約します。初期化により、メモリに有効なオブジェクト状態が与えられます。
Python オブジェクトの場合、この区別は重要です。
オブジェクトの割り当て:```text
reserve memory for object layout
set object header
set type pointer
set reference count
possibly track with GC
```オブジェクトの初期化:```text
fill fields
store references
validate arguments
establish invariants
```ユーザー定義クラスの場合:```python
obj = MyClass(1, 2)
```大まかなプロセスは次のとおりです。```text
call type machinery
allocate memory for instance
initialize object header
call __new__
call __init__
return initialized object
```C 拡張型の場合、割り当ては通常、型オブジェクトを介して行われます。```c
self = (MyObject *)type->tp_alloc(type, 0);
```次に、初期化によってフィールドが埋められます。
##11.4`tp_alloc`そして`tp_free`すべての型オブジェクトは、インスタンスの割り当てと解放の方法を指定できます。
重要なタイプ スロット:```text
tp_alloc
tp_free
tp_dealloc
tp_alloc新しいオブジェクト用にメモリを予約します。tp_freeオブジェクトのメモリを解放します。tp_dealloc型固有のデストラクターです。通常、フィールドを解放してから呼び出します。tp_free。
簡略化された形状:```c static void MyObject_dealloc(MyObject *self) { Py_XDECREF(self->value); Py_TYPE(self)->tp_free((PyObject *)self); }
この分離により、タイプ固有のクリーンアップを明示的に保ちながら、オブジェクトの種類ごとに異なる割り当て戦略を使用できるようになります。
## 11.5 スモールオブジェクトアロケータ
CPython のスモール オブジェクト アロケーターは一般に次のように呼ばれます。`pymalloc`。
Python オブジェクトによって使用される小さなメモリ ブロック向けに最適化されています。
概念的には:```text
arena
large region obtained from system allocator
pool
fixed-size subdivision inside an arena
block
one small allocation served to CPython
```階層:```text
system allocator
↓
arenas
↓
pools
↓
blocks
```小さい割り当てはサイズ クラスに丸められます。プールは 1 つのサイズ クラスのブロックを提供します。
これにより、システム アロケータにすべての小さなオブジェクトを要求することがなくなります。
## 11.6 アリーナ
アリーナは、基礎となるアロケーターから取得される大きなメモリ領域です。
概念的には:```text
arena
pool
pool
pool
...
```Arena を使用すると、CPython は多数の小さなオブジェクトの割り当てをバッチで管理できます。
CPython は、小さなオブジェクト用にさらに多くのメモリを必要とする場合、アリーナを要求します。あのアリーナはいくつかのプールに分かれています。プールはブロックを提供するために使用されます。
アリーナは、その中のすべてのプールが空になった場合にのみシステムに戻すことができます。これは、多くのオブジェクトが破棄された後でも、メモリが CPython によって予約されたままになる可能性があることを意味します。
その動作はユーザーを驚かせる可能性があります。```text
objects were freed
process RSS did not immediately shrink
```これは必ずしも漏れを示しているわけではありません。メモリは再利用のためにアロケータによって保持される場合があります。
## 11.7 プール
プールはアリーナの一部です。
各プールは一度に 1 つのブロック サイズ クラスを処理します。
例えば:```text
pool A
32-byte blocks
pool B
64-byte blocks
pool C
128-byte blocks
```プールがサイズ クラスに割り当てられると、そのプール内のすべてのブロックは同じサイズになります。これにより、割り当てと解放の操作が簡単になります。
アロケータは、使用可能なブロックを含むプールのリストを維持できます。小さなオブジェクトを割り当てるということは、多くの場合、プールから次に利用可能なブロックを取得することを意味します。
## 11.8 ブロックとサイズクラス
ブロックは、1 つの割り当てリクエストに対して返されるメモリです。
小さい割り当てサイズはサイズ クラスに切り上げられます。
コンセプトの例:```text
request 37 bytes
rounded to 40 or 48 byte class depending on allocator rules
request 72 bytes
rounded to matching size class
request too large
bypass pymalloc and use larger allocator path
```正確なサイズのクラスは、CPython のバージョンとビルド構成によって異なります。
重要な考え方:```text
small requests use fixed-size pools
large requests use another allocator path
```固定サイズのプールにより、割り当てが高速化され、小さなオブジェクトのワークロード内の断片化が軽減されます。
## 11.9 フリーリスト
一部のオブジェクト タイプでは、一般的なアロケータに加えてフリー リストが使用されます。
フリー リストは、特定のタイプの最近破棄されたオブジェクトをキャッシュして、すぐに再利用できるようにします。
CPython の歴史全体にわたる一般的な例には、フレーム、特定のサイズのタプル、浮動小数点数、リスト、その他の内部オブジェクトが含まれますが、正確なフリーリストの使用法はバージョンによって異なります。
概念的なフロー:```text
destroy object
if type-specific free list has room:
put object memory on free list
else:
return memory to allocator
create object
if free list has cached object:
reuse it
else:
allocate new memory
```フリーリストはメモリ保持と引き換えに速度を確保します。
これらにより、タイトなループでのオブジェクトの割り当てが大幅に高速化されますが、解放されたオブジェクトがすぐにメモリをアロケータに返さない可能性があることも意味します。
## 11.10 インターンとオブジェクトの再利用
一部のオブジェクトは意図的に再利用されます。
例としては次のものが挙げられます。```text
None
True
False
small integers
some strings
empty tuple
interned identifiers
```この再利用により、割り当てのプレッシャーが軽減され、一部の内部パスでのより高速な比較が可能になります。
例:```python
a = "name"
b = "name"
```文字列の作成方法によっては、CPython が文字列をインターンする場合があります。インターンされた文字列は、内部で使用される識別子、属性名、辞書キーに役立ちます。
オブジェクトの再利用は最適化です。 Python コードは、次のような文書化されたシングルトンを除き、オブジェクト ID に依存すべきではありません。`None`、`True`、`False`、`NotImplemented`、 そして`Ellipsis`。
正しい:```python
if value is None:
...
```避ける:```python
if x is 1000:
...
```2 つ目は、実装固有のオブジェクトの再利用動作に依存します。
## 11.11 不滅のオブジェクトと割り当て
最新の CPython は、選択されたランタイム所有のオブジェクトに対して不滅オブジェクトを使用します。
不滅のオブジェクトは永久に生きているものとして扱われます。参照カウント操作により、通常の存続期間への影響を回避できる可能性があります。
これは割り当てに間接的に影響します。```text
some fundamental objects are allocated once
their lifetime is the runtime lifetime
normal deallocation never frees them
```例としては、シングルトンのようなオブジェクトや頻繁に再利用される内部定数などが挙げられます。
拡張機能の作成者の場合、ルールは変わりません。```text
use Py_INCREF and Py_DECREF
do not manually inspect or change ob_refcnt
do not assume ordinary deallocation for every object
```オブジェクトが死滅するか不滅であるかに関係なく、正しいコードは機能します。
## 11.12 メモリの断片化
メモリの断片化は、空きメモリは存在するが、より大きな割り当て要求を満たすことができない、またはオペレーティング システムにきれいに返すことができない部分に分割されている場合に発生します。
CPython では、いくつかのレベルで断片化が発生する可能性があります。```text
inside pymalloc pools
inside arenas
inside the system allocator
inside type-specific free lists
inside long-lived Python containers
```パターン例:```python
items = []
for i in range(1_000_000):
items.append(bytearray(100))
del items
```Python オブジェクトは破棄される可能性がありますが、メモリの動作はオブジェクトのサイズ、アロケータ パス、アリーナの満杯度、フリー リスト、およびシステム アロケータの動作によって異なります。
CPython は後でメモリを再利用することを想定しているため、RSS は高いままになる可能性があります。
##11.13`tracemalloc`
`tracemalloc`Python のメモリ割り当てをトレースします。
例:```python
import tracemalloc
tracemalloc.start()
data = [str(i) for i in range(100_000)]
current, peak = tracemalloc.get_traced_memory()
print(current, peak)
tracemalloc.stop()
```メモリが割り当てられた場所を表示できます。```python
import tracemalloc
tracemalloc.start()
data = [bytes(1024) for _ in range(1000)]
snapshot = tracemalloc.take_snapshot()
stats = snapshot.statistics("lineno")
for stat in stats[:10]:
print(stat)
tracemallocPython レベルの割り当てのデバッグに役立ちます。すべての C ライブラリによって行われたすべてのネイティブ割り当てが表示されるわけではありません。
11.14 アロケータフックのデバッグ
CPython は、アロケーターの誤用を発見するのに役立つデバッグ メモリ フックをサポートしています。
デバッグ ビルドおよびデバッグ アロケーター モードでは、次のような問題を検出できます。```text writing before allocated memory writing after allocated memory using memory after free freeing memory with wrong allocator family double free uninitialized memory patterns
デバッグ フックは多くの場合、割り当ての周囲にパディング バイトを追加し、認識可能なバイト パターンでメモリを埋めます。
これにより、メモリ破損が発生源付近で発見されやすくなります。
## 11.15 アロケーター家族の規律
アロケーターの家族の規律は厳格です。
正しいペア:
|割り当てる |無料 |
| ----------------- | --------------- |
|`PyMem_RawMalloc` | `PyMem_RawFree` |
| `PyMem_Malloc` | `PyMem_Free` |
| `PyObject_Malloc` | `PyObject_Free` |
| `malloc` | `free`|
間違ったペアリングはバグです。```c
void *p = PyObject_Malloc(64);
PyMem_Free(p); /* wrong */
```また間違っています:```c
void *p = malloc(64);
PyObject_Free(p); /* wrong */
```メモリを作成するアロケータは、メモリを解放するアロケータである必要があります。
## 11.16 拡張コードでのバッファの割り当て
非オブジェクト バッファーの場合は、適切な Python アロケーター ファミリを優先してください。
例:```c
typedef struct {
PyObject_HEAD
char *data;
Py_ssize_t size;
} BufferObject;
```割り当て:```c
self->data = PyMem_Malloc(size);
if (self->data == NULL) {
PyErr_NoMemory();
return -1;
}
self->size = size;
```割り当て解除:```c
static void
Buffer_dealloc(BufferObject *self)
{
PyMem_Free(self->data);
Py_TYPE(self)->tp_free((PyObject *)self);
}
```バッファが使用するのは、`PyMem_*`。オブジェクト自体は型の`tp_alloc`そして`tp_free`。
これらの生涯を分離してください。
## 11.17 オブジェクトメモリと含まれる参照
オブジェクト メモリを割り当てても、含まれる Python 参照は自動的に管理されません。
例:```c
typedef struct {
PyObject_HEAD
PyObject *value;
} BoxObject;
```割り当てによりメモリが確保されます。`value`ですが、まだ有効な参照を所有していません。
初期化では安全に設定する必要があります。```c
self->value = NULL;
```次に、所有する参照を次のように割り当てます。`Py_INCREF`または、API コントラクトに従って盗まれた/新しい参照を受け取ることによって。
割り当てを解除するには、所有されている参照を解放する必要があります。```c
Py_XDECREF(self->value);
```メモリ割り当てと参照所有権は関連していますが、別個のシステムです。
## 11.18 割り当ての失敗
アロケータは失敗する可能性があります。
C 拡張コードは割り当て結果をチェックする必要があります。```c
void *p = PyMem_Malloc(size);
if (p == NULL) {
PyErr_NoMemory();
return NULL;
}
```オブジェクト作成関数については、`NULL`通常は、例外が設定されているか、例外を設定する必要があることを意味します。
正しいパターン:```c
PyObject *obj = PyLong_FromLong(42);
if (obj == NULL) {
return NULL;
}
```割り当てが成功したとは決して考えないでください。 Python コードは、メモリ負荷、組み込み環境、制約のあるコンテナー、またはファジング テストの下で実行できます。
## 11.19 再割り当て`PyMem_Realloc`メモリブロックのサイズを変更します。
パターン:```c
char *new_data = PyMem_Realloc(self->data, new_size);
if (new_data == NULL) {
PyErr_NoMemory();
return -1;
}
self->data = new_data;
self->size = new_size;
```成功を確認する前に、元のポインタを上書きしないでください。
正しくない:```c
self->data = PyMem_Realloc(self->data, new_size);
if (self->data == NULL) {
return -1; /* old pointer lost */
}
```再割り当てが失敗した場合でも、元の割り当ては有効なままになります。そのポインタを失うとメモリ リークが発生します。
## 11.20 過剰割り当て
一部のコンテナーは、追加のたびに再割り当てを避けるために過剰に割り当てます。
リストは標準的な例です。```python
xs = []
for i in range(100):
xs.append(i)
```リストでは、追加ごとに新しいスロットが 1 つだけ割り当てられるわけではありません。容量はより大きな段階で増加します。
概念的には:```text
length = number of used entries
allocated = number of available slots
```長さが割り当てられた容量に達すると、CPython は項目配列を拡大します。
これにより、償却された効率的な追加動作が実現します。
トレード・オフ:```text
fewer reallocations
some unused spare capacity
```## 11.21 コンテナの縮小
コンテナーは縮小してもすぐにメモリを返さない場合があります。
例:```python
xs = list(range(1_000_000))
del xs[:900_000]
```論理長が短くなります。特定の条件下でのみ内部容量が減少する場合があります。
これにより、リストの縮小と拡大を繰り返した場合のコストのかかる再割り当てが回避されます。
メモリに依存するコードの場合、新しいコンパクトなコンテナーを作成すると、次のことが役立つ場合があります。```python
xs = xs[:]
```または:```python
xs = list(xs)
```ただし、まず測定してください。コピーには費用がかかる場合があります。
## 11.22 メモリビューとバッファ
一部のオブジェクトはバッファ プロトコルを通じてメモリを公開します。
例:```text
bytes
bytearray
array.array
memoryview
mmap objects
NumPy arrays
some extension objects
```バッファ エクスポータは、生のメモリを別のオブジェクトに公開する場合があります。これにより、生涯にわたる制約が生じます。
例:```python
b = bytearray(b"hello")
v = memoryview(b)
```メモリビューが存在する間は、基礎となるバイト配列のサイズ変更が制限される場合があります。
C レベルでは、バッファ エクスポータは、コンシューマがバッファ ビューを保持している間、メモリが有効であることを保証する必要があります。
外部ビューがメモリに依存している間はメモリを解放または移動できないため、これはアロケータに関連しています。
## 11.23 非移動アロケータの結果
CPython のオブジェクト ポインターは安定しています。通常、オブジェクトは圧縮ガベージ コレクターによって移動されません。
結果:```text
PyObject * pointers remain valid while references are owned
C extensions can store object pointers
id(obj) can be address-like in CPython
memory cannot be compacted by moving live objects
fragmentation can accumulate
```動かない設計は、C API 互換性の中心となります。
また、CPython が圧縮ヒープではなくアリーナ、プール、フリー リスト、および慎重なアロケーターの階層化を使用する理由も説明します。
## 11.24 アロケーターのカスタマイズ
CPython を使用すると、エンベッダーと特殊な環境でアロケーターをカスタマイズできます。
これは次の場合に役立ちます。```text
embedding Python in another application
sandboxing
memory accounting
debugging
custom allocation strategies
instrumentation
constrained runtimes
```アロケーターのカスタマイズは慎重に、通常は実行時の初期化の早い段階で行う必要があります。
置換アロケータは、各アロケータ ドメインに対する CPython の期待に従う必要があります。
不正なアロケータ フックはインタープリタを破損する可能性があります。
## 11.25 メモリのアカウンティングは難しい
複数のレイヤーが相互作用するため、Python のメモリ使用量を理解するのは困難です。
単一の Python オブジェクトには次のものが含まれる場合があります。```text
object header
object payload
auxiliary arrays
referenced objects
allocator padding
pool overhead
arena overhead
free-list retention
system allocator metadata
native library allocations
```例:```python
xs = ["abc" for _ in range(1000)]
```メモリには次のものが含まれます。```text
list object
list item array
1000 references in the array
string objects
string character data
allocator overhead
possibly interned or reused objects
sys.getsizeof(xs)は、完全な推移グラフではなく、リスト オブジェクトとその直接ストレージのサイズのみを報告します。
##11.26sys.getsizeof
sys.getsizeof1 つのオブジェクトによって報告される、1 つのオブジェクトのサイズを返します。```python
import sys
xs = [1, 2, 3] print(sys.getsizeof(xs))
例:```python
import sys
xs = [[1], [2], [3]]
print(sys.getsizeof(xs))
```これには、内部リストとその内容ではなく、外部リストのストレージが含まれます。
再帰的なサイズ関数は、参照を注意深く走査し、共有オブジェクトの二重カウントを避ける必要があります。
## 11.27 一般的な割り当てパターン
一般的な CPython 割り当てパターンは次のとおりです。
|パターン |例 |割り当て動作 |
| ------------------- | ----------------------- | -------------------------------------------- |
|多数の小さなタプル |パーサー、AST 作業、ループ |スモールオブジェクトアロケータとフリーリストは重要 |
|大きなバイトのオブジェクト | I/O、シリアル化 |スモールオブジェクトアロケータをバイパスする可能性があります。
|成長するリスト |追加の多いコード |過剰割り当ての問題 |
|大きな辞書 |インデックス作成、JSON、グローバル |ハッシュ テーブルの増加が重要 |
|フレーム |関数呼び出し |フレームの割り当てと再利用が重要 |
|例外 |エラーの多いパス |トレースバックとフレーム保持が重要 |
|文字列 |識別子、解析 | Unicode レイアウトとインターンは重要 |
パフォーマンスの作業は、多くの場合、どのパターンが支配的であるかを見つけることから始まります。
## 11.28 C 拡張子の割り当てルール
拡張機能作成者のための実際的なルール:
|状況 |ルール |
| ----------------------------------- | -------------------------------------- |
| Python オブジェクト インスタンスの割り当て |タイプ割り当て機構を使用する |
| Python オブジェクト インスタンスを解放する |使用`tp_free`デアロケーターから |
|補助ランタイム メモリの割り当て |使用`PyMem_*`または文書化された家族 |
|オブジェクト メモリを手動で割り当てる |使用`PyObject_*`適切な場合のみ |
| Python オブジェクトを返す |所有されている参照を返す |
| Python オブジェクト フィールドの保存 |リファレンスを所有する |
|メモリの再割り当て |成功するまで古いポインタを保持する |
|割り当て失敗の処理 |設定または伝播`MemoryError`|
|ミキシングアロケータ |それはしないでください |
アロケーターのバグは多くの場合深刻です。これらは、実際の間違いから遠く離れたクラッシュとして現れる場合があります。
## 11.29 メンタルモデル
このモデルを使用します。```text
Python object allocation
type object chooses allocation path
object memory contains common header
object-specific fields are initialized
references are owned explicitly
deallocator releases references
tp_free releases memory
Small-object allocation
arenas contain pools
pools contain fixed-size blocks
small requests are served quickly
memory may be retained for reuse
```CPython のメモリ管理は階層化されたシステムです。```text
reference counting decides when an object dies
cyclic GC finds unreachable cycles
deallocator releases object-owned resources
allocator reuses or frees memory blocks
system allocator manages process heap pages
operating system manages virtual memory
```各レイヤーは異なる質問に答えます。
## 11.30 概要
CPython のメモリ アロケータ システムは、Python プログラムのオブジェクト中心のワークロードに対する高速割り当てをサポートします。小さなオブジェクトは通常、次の方法で提供されます。`pymalloc`、メモリをアリーナ、プール、ブロックに編成します。タイプ固有のフリー リストとオブジェクトの再利用により、共通オブジェクトの割り当てコストがさらに削減されます。
C 拡張機能の作成者にとって重要なルールは、アロケーター ファミリの規律、割り当て失敗の正しい処理、安全な再割り当て、メモリ所有権と参照所有権の明確な分離です。
Python オブジェクト レベルで解放されたメモリは、CPython またはプラットフォーム アロケーター内で予約されたままになる場合があります。オブジェクト削除後の RSS の高さが自動的にリークを意味するわけではありません。 CPython は多くの場合、メモリを再利用できるように保持します。