メモリ割り当てしたい(G4Allocator<T>

1// 書式: G4ThreadLocal G4Allocator<T>* allocator;
2// 例: SensorHit型のアロケーター
3
4// include/SensorHit.hh で宣言する
5extern G4ThreadLocal G4Allocator<SensorHit>* SensorHitAllocator;
6
7// src/SensorHit.cc で定義する(nullptrで実体化)
8G4ThreadLocal G4Allocator<SensorHit>* SensorHitAllocator = nullptr;

G4Allocator<T>はGeant4の高速メモリ管理機能です。 高頻度で生成・破棄されるオブジェクトのメモリ確保と解放の効率を最適化する メモリプール型のアロケーター であり、 G4StepG4Trackの処理などGeant4内部でも利用されています。

ユーザーが明示的に利用する場面は、 前述したユーザー定義のヒットクラスです。 対象のユーザー定義クラスで operator new()operator delete() をオーバーロードし、 その内部でMallocSingle() / FreeSingle() を呼び出して利用します。

注釈

通常のnewおよびdelete演算子は、動的にメモリを確保・解放するためオーバーヘッドが生じます。 Geant4のイベント処理では、オブジェクトを大量に生成・削除するため、このオーバーヘッドが大きくなります。 G4Allocatorであらかじめ確保したメモリブロックを使い回すことで、このオーバーヘッドを大幅に軽減します。

G4ThreadLocalと併用することで、G4Allocatorをスレッドごとに分離でき、 マルチスレッド環境でも安全に利用できます。

メモリを割り当てたい(MallocSingle

1SensorHitAllocator->MallocSingle();

MallocSingleは、G4Allocatorが管理しているメモリプールから、T型(ここではSensorHit型)オブジェクト1つ分のメモリを割り当てる関数です。 これはC++標準のmallocに相当します。

通常は、以下のように new演算子(operator new)をオーバーロードして使います。

1void* SensorHit::operator new(size_t)
2{
3    if(!SensorHitAllocator) {
4        SensorHitAllocator = new G4Allocator<SensorHit>;
5    }
6    return (void *) SensorHitAllocator->MallocSingle();
7};

operator new()は、コンストラクターが呼ばれるまえに実行される特殊関数です。 ここでSensorHitAllocatorを初期化し、必要なメモリを確保しています。

メモリを解放したい(FreeSingle

1SensorHitAllocator->FreeSingle(型名)

FreeSingleは、MallocSingleで割り当てたメモリを再びメモリプールに返す関数です。 C++標準のfreeに相当します。

通常は、delete演算子(operator delete)をオーバーロードして使います。

1void SensorHit::operator delete(void *hit)
2{
3    SensorHitAllocator->FreeSingle((SensorHit*)hit);
4};

operator delete()演算子は、デストラクターの後に実行される特殊関数です。 前述のnew演算子で割り当てたメモリを、ここでアロケーターに返却しています。

ヘッダーファイル(include/SensorHit.hh

 1#include "G4VHit.hh"
 2#include "G4Allocator.hh"
 3
 4class SensorHit : public G4VHit
 5{
 6  public:
 7    void* operator new(size_t);
 8    void operator delete(void*);
 9    // その他のメンバー変数や関数
10};
11
12//////////////////////////////////////////////////
13// (スレッドローカルな)グローバル変数を宣言
14// 1. スレッドごとにSensorHit型のメモリを管理するためのアロケータ
15// 2. そのメモリ領域を SensorHitAllocator という名前で管理する
16// 3. 実体の定義は .cc ファイルに記述する
17extern G4ThreadLocal G4Allocator<SensorHit>* SensorHitAllocator;

G4Allocatorはユーザー定義のヒットクラスと同じファイルで宣言します。 SensorHitAllocatorのようにグローバルに定義することで、 どこからでもMallocSingle()FreeSingle()を呼び出して効率的にメモリを管理できます。

Geant4はマルチスレッド環境がデフォルトになっているため、 各スレッドが独立してアロケーターを保持できるようにG4ThreadLocalを併用します。

また、C++ではグローバル変数を複数のファイルから参照する場合、 externを使って宣言する必要があります。

ソースファイル(src/SensorHit.cc

 1#include "SensorHit.hh"
 2
 3G4ThreadLocal G4Allocator<SensorHit>* SensorHitAllocator = nullptr;
 4
 5void* SensorHit::operator new(size_t)
 6{
 7    // new演算子でメモリを割り当てるとき
 8    // {
 9    //     SensorHit *hit = new SensorHit();
10    // }
11    // 1. SensorHitAllocatorに割り当てられたメモリのポインターを取得する
12    // 2. 初期化されてない場合は、新しくG4Allocator<SensorHit>オブジェクトを作成
13    if(!SensorHitAllocator) {
14        SensorHitAllocator = new G4Allocator<SensorHit>;
15    }
16    return (void *) SensorHitAllocator->MallocSingle();
17};
18
19
20//////////////////////////////////////////////////
21void SensorHit::operator delete(void *hit)
22{
23    // delete演算子でメモリを解放するとき
24    //
25    // {
26    //     SensorHit *hit = new SensorHit();
27    //     delete hit
28    // }
29    //
30    // 1. SensorHitAllocatorに割り当てられたメモリのポインターを解放する
31    SensorHitAllocator->FreeSingle((SensorHit*)hit);
32};

SensorHitAllocatornullptrで初期化します。

newするときは、SensorHitAllocatorがまだ確保できていない場合のみ、 new G4Allocator<SensorHit>でアロケーターを生成し、 SensorHitAllocator->MallocSingle()でメモリプールからオブジェクト1つ分の領域を確保します。

deleteするときは、オブジェクトのメモリをOSに返却せず、 SensorHitAllocator->FreeSingle(...)でメモリプールに返却し、 再利用できる状態にします。

割り当てサイズをしりたい(GetAllocatedSize

1// 割り当てられた合計サイズ
2G4int size = SensorHitAllocator->GetAllocatedSize();
3
4// 現在のページのサイズ
5G4int page_size = SensorHitAllocator->GetPageSize();
6
7// 割り当てられたページ数
8G4int n_pages = SensorHitAllocator->GetNoPages();

GetAllocatedSizeで、割り当てられたメモリの合計サイズを確認できます。

リファレンス