質問

カスタムソリューションを優先して std :: allocator を捨てるには、いくつかの本当に良い理由は何ですか?正確性、パフォーマンス、スケーラビリティなどのために絶対に必要な状況に遭遇しましたか?本当に賢い例はありますか?

カスタムアロケーターは、私があまり必要としていない標準ライブラリの機能でした。ここSOの誰かが彼らの存在を正当化する説得力のある例を提供できるのではないかと思っていました。

役に立ちましたか?

解決

こちらに言及したように、Intel TBBのカスタムSTLを見ましたアロケータは、単一の

を変更するだけで、マルチスレッドアプリケーションのパフォーマンスを大幅に改善します。
std::vector<T>

to

std::vector<T,tbb::scalable_allocator<T> >

(これは、TBBの気の利いたスレッドプライベートヒープを使用するようにアロケーターを切り替える迅速かつ便利な方法です。このドキュメントの7ページ

他のヒント

カスタムアロケーターが役立つ可能性のある領域の1つは、特にゲームコンソールでのゲーム開発です。これらは、メモリがわずかでスワップがありません。このようなシステムでは、重要でないシステムが重要なシステムからメモリを盗むことができないように、各サブシステムを厳密に制御する必要があります。プールアロケーターのような他のものは、メモリの断片化を減らすのに役立ちます。トピックに関する長く詳細なペーパーは、次の場所にあります。

EASTL-電子芸術標準テンプレートライブラリ

ベクターがメモリを使用できるようにするmmap-allocatorで作業しています メモリマップドファイル。目標は、ストレージを使用するベクトルを持つことです mmapによってマップされた仮想メモリに直接あります。私たちの問題は 本当に大きなファイル(&gt; 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が使用するように設定されたユーザーとしてメモリを使用していることを確認できます。

カスタムアロケーターを使用して、ヒープの代わりにメモリプールを使用すると便利です。これは他の多くの例の1つです。

ほとんどの場合、これは確かに時期尚早な最適化です。ただし、特定のコンテキスト(組み込みデバイス、ゲームなど)では非常に便利です。

カスタムSTLアロケーターを使用してC ++コードを作成していませんが、HTTPリクエストへの応答に必要な一時データの自動削除にカスタムアロケーターを使用するC ++で作成されたWebサーバーを想像できます。応答が生成されると、カスタムアロケーターはすべての一時データを一度に解放できます。

カスタムアロケーターの別の使用例(私が使用した)は、関数の動作が入力の一部に依存しないことを証明する単体テストを作成しています。カスタムアロケーターは、メモリ領域を任意のパターンで埋めることができます。

GPUまたは他のコプロセッサを使用する場合、特別な方法でメインメモリにデータ構造を割り当てることが有益な場合があります。メモリを割り当てるこの特別な方法は、便利な方法でカスタムアロケータに実装できます。

アクセラレータを使用する場合、アクセラレータランタイムを介したカスタム割り当てが有益である理由は次のとおりです。

  1. カスタム割り当てにより、アクセラレータランタイムまたはドライバにメモリブロックが通知されます
  2. さらに、オペレーティングシステムは、割り当てられたメモリブロックがページロックされていることを確認できます(これはピンメモリと呼ばれます)。つまり、オペレーティングシステムの仮想メモリサブシステムは移動できません。またはメモリ内またはメモリからページを削除します
  3. 1および2.が保持され、ページロックメモリブロックとアクセラレータ間のデータ転送が要求された場合、ランタイムはメインメモリ内のデータに直接アクセスできます。システムが移動/削除しませんでした
  4. これにより、ページロックされない方法で割り当てられたメモリで発生するメモリコピーが1つ保存されます。データは、メインメモリでページロックされたステージング領域にコピーする必要があります。 (DMA経由)

ここではカスタムアロケーターを使用しています。他のカスタムダイナミックメモリ管理を回避することであるとさえ言うかもしれません。

背景: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ビット整数を保持することで実現しました。ここでは、プロセス全体を高速化するためにメモリ使用量と引き換えになりましたが、この場合、このマイクロコントローラーチップを最大処理能力に近づけるようにしていたため、これは正当化されました。

他にも、組み込みシステムのコンテキストで独自のカスタムアロケータを記述していることがあります。たとえば、これらのプラットフォーム

共有メモリの場合、コンテナヘッドだけでなく、コンテナヘッドに含まれるデータも共有メモリに保存することが重要です。

Boostのアロケーター:: Interprocess は良い例です。ただし、ここでは、すべてのSTLコンテナが共有メモリと互換性を持たせるためには、これだけでは不十分です(異なるプロセスの異なるマッピングオフセットのため、ポインタが「壊れる」可能性があります)。

アロケーターに関するAndrei AlexandrescuのCppCon 2015トークへの必須リンク:

https://www.youtube.com/watch?v=LIb3L4vKZ7U

良い点は、それらを考案するだけで、それらをどのように使用するかのアイデアを考えるようになることです:-)

以前、このソリューションが非常に役立つことがわかりました: STLコンテナ用の高速C ++ 11アロケーター。 VS2017(〜5倍)およびGCC(〜7倍)でSTLコンテナをわずかに高速化します。これは、メモリプールに基づく特別な目的のアロケーターです。求めているメカニズムのおかげで、STLコンテナでのみ使用できます。

私は個人的にLoki :: Allocator / SmallObjectを使用して、小さなオブジェクトのメモリ使用量を最適化します&#8212;中程度の量の非常に小さなオブジェクト(1〜256バイト)で作業しなければならない場合、優れた効率と満足のいくパフォーマンスを示します。多くの異なるサイズの小さなオブジェクトの適度な量の割り当てについて話す場合、標準のC ++のnew / delete割り当てよりも最大30倍効率的です。また、「QuickHeap」と呼ばれるVC固有のソリューションがあり、最高のパフォーマンスをもたらします(割り当ておよび割り当て解除操作は、最大99でそれぞれヒープに割り当て/返されるブロックのアドレスを読み書きするだけです)(9)%ケース&#8212;は設定と初期化に依存します)が、顕著なオーバーヘッドが発生します&#8212;エクステントごとに2つのポインターが必要で、新しいメモリブロックごとに1つ余分に必要です。さまざまなオブジェクトサイズが必要ない場合に作成および削除される膨大な(10000 ++)オブジェクトを操作するための最速のソリューションです(1〜1023バイトのオブジェクトサイズごとに個別のプールを作成します)現在の実装では、初期化コストは全体的なパフォーマンスの向上をわずかに抑えますが、アプリケーションがパフォーマンスクリティカルなフェーズに入る前に、いくつかのダミーオブジェクトを割り当て/割り当て解除できます。

標準のC ++ new / delete実装の問題は、通常はC malloc / free割り当ての単なるラッパーであり、1024 +バイトなどの大きなメモリブロックに対して適切に機能することです。パフォーマンスの面で顕著なオーバーヘッドがあり、場合によってはマッピングにも余分なメモリが使用されます。そのため、ほとんどの場合、カスタムアロケーターは、パフォーマンスを最大化する、および/または小さな(&#8804; 1024バイト)オブジェクトを割り当てるために必要な追加メモリの量を最小化する方法で実装されます。

グラフィックシミュレーションでは、カスタムアロケーターが使用されているのを見ました

  1. std :: allocator が直接サポートしなかったアライメント制約。
  2. 短命(このフレームのみ)と長命の割り当てに別々のプールを使用して、断片化を最小限に抑えます。
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top