最終更新日時: 2024年06月11日 22時45分38秒 (JST)
Akira Takahashi が更新

履歴 編集

class
<memory_resource>

# std::pmr::pool_resource(C++17)

    namespace std::pmr {
      class synchronized_pool_resource;
      class unsynchronized_pool_resource;
    }

## 概要

`pool_resource`は幾つかのブロックサイズ毎のメモリプールを管理し、要求サイズ毎に最適なプールからメモリを割り当てる`memory_resource`実装である。
メモリ割り当て要求に内部のメモリプールから応えられない場合（最大のブロックサイズを超える要求があった場合）は、上流のメモリリソースからメモリを割り当てる。ほとんどの場合、上流メモリリソースへの割り当て要求は内部プールへの割り当て要求よりも少ない。

以下2つの`pool_resource`が提供される。

`pool_resource`クラス名 | 説明 | 対応バージョン
--- | --- | ---
`synchronized_pool_resource` | スレッドセーフな`pool_resource` | C++17
`unsynchronized_pool_resource` | スレッドセーフでない`pool_resource` | C++17

`synchronized_pool_resource`は外部同期機構無しで複数のスレッドからスレッドセーフにアクセスできる。
実装によっては同期コストの削減のためにスレッド毎にメモリプールを持つ可能性がある。

`unsynchronized_pool_resource`は複数スレッドからアクセスした際にスレッドセーフ性が保証されないが、シングルスレッドで使用する場合に同期のためのオーバーヘッドを避けることができる。

## `pool_resource`の性質

- 内部管理メモリ領域は様々なブロックサイズのメモリプールに分かれている。
- それぞれのブロックサイズ毎のメモリプールは幾つかのチャンクの集まりであり、各チャンクは均一なサイズ（ブロックサイズ）のブロックに順番に分割されている。
- `do_allocate`によって返されるメモリはブロック単位で割り当てられる。
- `do_allocate(bytes, alignment)`は、`bytes`以上で最小のブロックサイズのプールからメモリを割り当てる。
  - `bytes`が最大ブロックサイズを超える場合、上流メモリリソースから直接割り当てられる。
- あるプールが枯渇した場合、次の`do_allocate`時に上流メモリリソースからチャンク単位でプールを補充する。
  - 補充の度に補充されるチャンクサイズは等比数列に従って増加する（上限有）。
  - 一定のチャンク毎に補充を行うことで連続した割り当てにおける参照局所性を高める狙いがある。
- 最大チャンクサイズ及び最大ブロックサイズは`pool_options`構造体をコンストラクタに渡して調整可能。
  - `max_blocks_per_chunk` : チャンクサイズの上限値、この値よりは大きくならない
  - `largest_required_pool_block` : ブロックサイズの最大値、この値を超える割り当て要求は上流メモリリソースから直接割り当てる
- 管理メモリの解放時は`deallocate`が呼び出されていない領域があっても全てのメモリを解放する。

## メンバ関数

### 構築・破棄

名前 | 説明 | 対応バージョン
--- | --- | ---
`(constructor)` | コンストラクタ | C++17
`(destructor)` | デストラクタ | C++17
`operator=(const pool_resource&) = delete;` | コピー代入演算子（コピー禁止） | C++17

クラス名を`pool_resource`としているのは説明のためのプレースホルダで、`synchronized_pool_resource`と`unsynchronized_pool_resource`で共通ということである。

### 共通メンバ関数

名前 | 説明 | 対応バージョン
--- | --- | ---
`release` | 割り当てられている全てのメモリを解放する | C++17
`upstream_resource` | 利用している上流`memory_resource`を取得する | C++17
`options` | 適用されているプール設定を`pool_options`として取得する | C++17

### 非仮想インターフェース（NVI）

名前 | 説明 | 対応バージョン
--- | --- | ---
`allocate` | メモリを確保する | C++17
`deallocate` | メモリを解放する | C++17
`is_equal` | オブジェクトを超えてメモリ領域の解放を行えるかを調べる | C++17

### protected オーバーライド関数

名前 | 説明 | 対応バージョン
--- | --- | ---
`do_allocate` | メモリを確保する | C++17
`do_deallocate` | メモリを解放する | C++17
`do_is_equal` | オブジェクトを超えてメモリ領域の解放を行えるかを調べる | C++17

## 非メンバ関数

名前 | 説明 | 対応バージョン
--- | --- | ---
`operator==` | 等値比較 | C++17
`operator!=` | 非等値比較 | C++17

## `synchronized_pool_resource`の処理系毎の実装

### MSVC 2017, 2019

`unsynchronized_pool_resource`を継承し、アクセスを`mutex`で同期させている。
スレッド毎にプールを持たない。

### GCC 9.1

スレッド毎にプールを持ち、各プールはリンクリストで管理されている。

## バージョン

### 言語

- C++17

### 処理系

- Clang: ??
- GCC: 9.1 ✅
- Visual C++: 2017 update 6 ✅

## 関連項目

- `pool_options`
- `memory_resource`

## 参照

- C++1z 多相アロケータとメモリプール - Faith and Brave - C++で遊ぼう
- memory_resourceについて - 本の虫
- Polymorphic Allocator in C++17 - Qita
- P0220R1 Adopt Library Fundamentals V1 TS Components for C++17 (R1)
- P0337r0 | Delete operator= for polymorphic_allocator
- Working Draft, C++ Extensions for Library Fundamentals, Version 2