回答:
ここで述べたように、インテルTBBのカスタムSTLアロケーターは、シングルスレッドを変更するだけでマルチスレッドアプリのパフォーマンスを大幅に向上させるのを見てきました
std::vector<T>
に
std::vector<T,tbb::scalable_allocator<T> >
(これは、TBBの気の利いたスレッドプライベートヒープを使用するようにアロケータを切り替える便利な方法です。このドキュメントの7ページを参照してください)
カスタムアロケーターが役立つ可能性がある1つの領域は、特にゲームコンソールでのゲーム開発です。これらはメモリが少なく、スワップがないためです。このようなシステムでは、重要でないシステムが重要なシステムからメモリを盗用できないように、各サブシステムを厳密に制御できるようにする必要があります。プールアロケーターのような他のものは、メモリの断片化を減らすのに役立ちます。このトピックに関する長くて詳細な論文は、次の場所にあります。
私は、ベクトルがメモリマップファイルのメモリを使用できるようにするmmap-allocatorに取り組んでいます。目標は、mmapによってマップされた仮想メモリに直接あるストレージを使用するベクトルを持つことです。私たちの問題は、非常に大きなファイル(> 10GB)のメモリへの読み取りをコピーオーバーヘッドなしで改善することです。そのため、このカスタムアロケーターが必要です。
これまでのところ、カスタムアロケーター(std :: allocatorから派生)のスケルトンを持っているので、独自のアロケーターを作成するのは良い出発点だと思います。このコードを自由に使用してください。
#include <memory>
#include <stdio.h>
namespace mmap_allocator_namespace
{
// See StackOverflow replies to this answer for important commentary about inheriting from std::allocator before replicating this code.
template <typename T>
class mmap_allocator: public std::allocator<T>
{
public:
typedef size_t size_type;
typedef T* pointer;
typedef const T* const_pointer;
template<typename _Tp1>
struct rebind
{
typedef mmap_allocator<_Tp1> other;
};
pointer allocate(size_type n, const void *hint=0)
{
fprintf(stderr, "Alloc %d bytes.\n", n*sizeof(T));
return std::allocator<T>::allocate(n, hint);
}
void deallocate(pointer p, size_type n)
{
fprintf(stderr, "Dealloc %d bytes (%p).\n", n*sizeof(T), p);
return std::allocator<T>::deallocate(p, n);
}
mmap_allocator() throw(): std::allocator<T>() { fprintf(stderr, "Hello allocator!\n"); }
mmap_allocator(const mmap_allocator &a) throw(): std::allocator<T>(a) { }
template <class U>
mmap_allocator(const mmap_allocator<U> &a) throw(): std::allocator<T>(a) { }
~mmap_allocator() throw() { }
};
}
これを使用するには、次のようにSTLコンテナを宣言します。
using namespace std;
using namespace mmap_allocator_namespace;
vector<int, mmap_allocator<int> > int_vec(1024, 0, mmap_allocator<int>());
たとえば、メモリが割り当てられたときにログを記録するために使用できます。必要なのは再バインド構造体です。それ以外の場合、ベクターコンテナーはスーパークラスの割り当て/割り当て解除メソッドを使用します。
更新:メモリマッピングアロケーターは、https://github.com/johannesthoma/mmap_allocatorから入手でき、LGPLです。プロジェクトに自由に使用してください。
コードにc ++を使用するMySQLストレージエンジンを使用しています。MySQLとメモリを競合するのではなく、カスタムアロケーターを使用してMySQLメモリシステムを使用しています。これにより、ユーザーがMySQLを使用するために設定したメモリとして、「余分」ではなくメモリを使用していることを確認できます。
カスタムSTLアロケーターを使用してC ++コードを記述していませんが、HTTPリクエストへの応答に必要な一時データの自動削除にカスタムアロケーターを使用するC ++で記述されたWebサーバーを想像できます。カスタムアロケータは、応答が生成されると、すべての一時データを一度に解放できます。
カスタムアロケーター(私が使用したもの)のもう1つの可能なユースケースは、関数の動作が入力の一部に依存しないことを証明する単体テストを作成することです。カスタムアロケーターは、メモリ領域を任意のパターンで埋めることができます。
GPUまたは他のコプロセッサーを使用する場合、特別な方法でデータ構造をメインメモリに割り当てると効果的な場合があります。メモリを割り当てるこの特別な方法は、便利な方法でカスタムアロケーターに実装できます。
アクセラレータを使用するときに、アクセラレータランタイムによるカスタム割り当てが有益である理由は次のとおりです。
ここではカスタムアロケーターを使用しています。他のカスタムの動的メモリ管理を回避するためだったとさえ言うかもしれません。
背景:malloc、calloc、free、およびオペレーターnewおよびdeleteのさまざまなバリアントのオーバーロードがあり、リンカーはSTLにこれらを使用させてくれます。これにより、自動小さなオブジェクトプーリング、リーク検出、割り当ての塗りつぶし、空き塗りつぶし、監視機能によるパディング割り当て、特定の割り当てのキャッシュラインアライメント、遅延解放などを実行できます。
問題は、組み込み環境で実行していることです。実際には、長期間にわたって適切にリーク検出アカウンティングを実行するのに十分なメモリがありません。少なくとも、標準のRAMにはありません-カスタムの割り当て関数を介して、他の場所に利用可能なRAMの別のヒープがあります。
解決策:拡張ヒープを使用するカスタムアロケーターを記述し、それのみを使用するメモリリーク追跡アーキテクチャの内部でます...それ以外はすべて、デフォルトで、リーク追跡を行う通常の新規/削除オーバーロードになります。これにより、トラッカー自体の追跡が回避されます(トラッカーノードのサイズがわかっているため、パッキング機能も少し追加されます)。
同じ理由で、これを使用して関数コストプロファイリングデータを保持します。各関数の呼び出しと戻り、およびスレッドの切り替えごとにエントリを書き込むと、コストがかかります。カスタムアロケータにより、デバッグメモリ領域が大きくなり、割り当てが小さくなります。
重要な状況の1つ:モジュール(EXE / DLL)の境界を越えて機能する必要のあるコードを作成する場合、割り当てと削除が1つのモジュールでのみ行われるようにすることが重要です。
私がこれに遭遇したのは、Windowsのプラグインアーキテクチャでした。たとえば、DLLの境界を越えてstd :: stringを渡した場合、文字列の再割り当ては、DLLのヒープではなく、元のヒープから行われることが重要です*。
* CRTに動的にリンクしているかのように動作する可能性があるため、実際にはこれよりも複雑です。ただし、各DLLにCRTへの静的リンクがある場合は、幻影の割り当てエラーが継続的に発生する痛みの世界に向かっています。
私がこれらを使用したときの1つの例は、非常にリソースに制約のある組み込みシステムでの作業でした。2kのRAMがなく、プログラムがそのメモリの一部を使用する必要があるとします。スタック上ではない場所に4〜5のシーケンスを格納する必要があります。さらに、これらのものが格納される場所を非常に正確にアクセスする必要があります。これは、独自のアロケーターを記述したい状況です。デフォルトの実装はメモリを断片化する可能性があり、十分なメモリがなく、プログラムを再起動できない場合、これは受け入れられない場合があります。
私が取り組んでいたプロジェクトの1つは、一部の低消費電力チップでAVR-GCCを使用することでした。可変長の8つのシーケンスを格納する必要がありましたが、既知の最大値がありました。メモリ管理の標準ライブラリ実装malloc / freeの薄いラッパーで、割り当てられたすべてのメモリブロックの先頭に、割り当てられたメモリの最後をちょうど過ぎたところへのポインタを付けることで、アイテムの配置場所を追跡します。新しいメモリを割り当てるとき、標準アロケータはメモリの各部分をウォークスルーして、要求されたメモリサイズが収まる次の使用可能なブロックを見つける必要があります。デスクトッププラットフォームでは、このいくつかの項目ではこれは非常に高速ですが、これらのマイクロコントローラーの一部は比較すると非常に低速でプリミティブであることを覚えておく必要があります。さらに、メモリの断片化の問題は非常に大きな問題であり、実際には別のアプローチを取るしかありませんでした。
だから私たちがやったことは私たち自身のメモリプールを実装することでした。メモリの各ブロックは、必要な最大のシーケンスに適合するのに十分な大きさでした。これにより、固定サイズのメモリブロックが事前に割り当てられ、現在使用されているメモリブロックがマークされます。これは、特定のブロックが使用された場合に各ビットが表す1つの8ビット整数を維持することで実現しました。ここでは、プロセス全体を高速化するためにメモリ使用量をトレードオフしました。この場合、このマイクロコントローラーチップを最大処理能力に近づけたため、これは正当化されました。
組み込みシステムのコンテキストで独自のカスタムアロケーターを作成しているのを目にすることがあるのは他にもたくさんあります。たとえば、シーケンスのメモリがメインRAMにない場合は、 これらのプラットフォームでです。
Andrei AlexandrescuのCppCon 2015への義務的なリンクはアロケーターについて話します:
https://www.youtube.com/watch?v=LIb3L4vKZ7U
良い点は、それらを考案するだけで、それらをどのように使用するかについてのアイデアが思い浮かぶことです:-)
共有メモリの場合、コンテナヘッドだけでなく、そこに含まれるデータも共有メモリに格納されることが重要です。
Boost :: Interprocessのアロケーターが良い例です。ただし、ここで読むことができるように、すべてのSTLコンテナを共有メモリに互換性を持たせるには、これだけでは不十分です(プロセスごとに異なるマッピングオフセットがあるため、ポインタが「壊れる」可能性があります)。
いつか私はこのソリューションが非常に役立つことに気づきました:STLコンテナー用の高速C ++ 11アロケーター。VS2017(〜5x)とGCC(〜7x)でSTLコンテナーをわずかに高速化します。これは、メモリプールに基づく特別な目的のアロケータです。それはあなたが求めているメカニズムのおかげでのみ、STLコンテナーで使用できます。
私は個人的にLoki :: Allocator / SmallObjectを使用して小さなオブジェクトのメモリ使用量を最適化します。中程度の量の本当に小さなオブジェクト(1〜256バイト)で作業する必要がある場合、効率と満足のいくパフォーマンスを示します。多くの異なるサイズの適度な量の小さなオブジェクトを割り当てる場合、標準のC ++の新規/削除の割り当てよりも最大で約30倍効率的です。また、「QuickHeap」と呼ばれるVC固有のソリューションがあり、可能な限り最高のパフォーマンスを提供します(割り当てと割り当て解除の操作は、ヒープに割り当てられている/返されているブロックのアドレスの読み取りと書き込みをそれぞれ最大99まで行います。(9)%の場合—設定と初期化に依存します)が、かなりのオーバーヘッドを犠牲にして—エクステントごとに2つのポインターと、新しいメモリブロックごとに1つの追加が必要です。それ'
標準のC ++ new / delete実装の問題は、通常、C malloc / free割り当ての単なるラッパーであり、1024以上のバイトなど、より大きなメモリブロックでうまく機能することです。パフォーマンスの面で顕著なオーバーヘッドがあり、マッピングに使用される追加のメモリも時々あります。そのため、ほとんどの場合、カスタムアロケータは、パフォーマンスを最大化するか、小さい(1024バイト以下)オブジェクトを割り当てるために必要な追加のメモリ量を最小化する方法で実装されます。
グラフィックシミュレーションでは、カスタムアロケータが
std::allocator
直接サポートしていない線形制約。