7

割り当てられたメモリ アドレスを再利用するメモリ プール クラスと、そのクラスをラップするカスタム アロケータを使用しています。次のコード スニペットは、インターフェイスの基本的な概念を示しています。

template<class alloc>
class memory_pool
    : boost::noncopyable,
      public allocator_traits<void>
{
public:
    memory_pool(typename alloc::size_type alloc_size);
    memory_pool(typename alloc::size_type alloc_size, alloc const&);
    template<typename U> memory_pool(typename alloc::size_type alloc_size,
        typename alloc::rebind<U>::other const&);
    virtual ~memory_pool();

    pointer allocate  (); /*throw(std::bad_alloc)*/
    void    collect   ();
    void    deallocate(pointer) throw(); /*noexcept*/
};

pointer allocate()
{/*
    Checks if a suitable chunk of memory is available in a internal linked list.
    If true, then the chunk is returned and the next chunk moves up.
    Otherwise, new memory is allocated by the underlying allocator.
*/}

void deallocate(pointer)
{/*
    Interprets the passed pointer as a chunk of memory and stores it in a linked list.
    Please note that memory isn't actually deallocated.
*/}

void collect()
{/*
    Effectively deallocates the cunks in the linked list.
    This will be called at least once during destruction.
*/}

確かに、このようなものの必要性は限られています。ただし、必要な状況では非常に便利です: - そのアロケーターを非常に素朴な方法で使用するクラスのアロケーター型を指定する (例: たとえそれが望ましい場合でも、より大きなピースの割り当てを回避する)。- 同じサイズのメモリの割り当てと割り当て解除を繰り返します。- アロケータを使用したい型のサイズが非常に小さい (例: char、short、int などの組み込み型)。

理論的には、実装は、実際の割り当てサイズの倍数を (基礎となるメモリ マネージャーから) 必要なたびに割り当てる memory_pool を利用できます。近接しているオブジェクトは、キャッシュやプリフェッチ アルゴリズムに適しています。私は、正しい割り当て、分割、および割り当て解除を処理するために、いくらかのオーバーヘッドを伴うこのようなメモリ プールを実装しました (ユーザーが割り当て解除に渡す各アドレスの割り当てを解除することはできません。各メモリ ブロックの先頭であるアドレスのみを割り当て解除する必要があります。以前に割り当てられている)。

次の非常に単純なコードを使用して、両方のケースをテストしました。

std::list<int, allocator<int>> list;

std::clock_t t = std::clock();
for (int i = 0; i < 1 << 16; ++i)
{
    for (int j = 0; j < 1 << 16; ++j)
        list.push_back(j);
    list.unique();
    for (int j = 0; j < 1 << 16; ++j)
        list.pop_back();
}
std::cout << (std::clock() - t) / CLOCKS_PER_SEC << std::endl;

std::listが呼び出さallocactor::allocate(1, 0)れるたびに呼び出していpush_backます。unique()各要素が触れられ、次の要素と比較されることを確認します。しかし、結果は残念でした。ブロックごとに割り当てるメモリプールを管理するために必要な最小限のオーバーヘッドは、システムが得る可能性のある利点よりも大きくなります。

パフォーマンスが向上するシナリオを考えられますか?

編集: もちろん、よりもはるかに高速ですstd::allocator

4

2 に答える 2

1

C++0x では、メモリ プールなどのスコープ アロケータのサポートが強化されています。

コードをプロファイリングします。アルゴリズムが LIFO などの非常に規則的な割り当て/割り当て解除パターンを実行しない限り、これがもたらす利点を予測することは非常に困難です。

割り当てられたすべてのオブジェクトが同じサイズである場合、非常に高速なアロケーターを作成するのは非常に簡単です。かつて私はの線に沿って何かを書いた

template <size_t ObjectSize> class allocator {
    // ...
};

template <typename T> class allocator : public allocator <sizeof (T)> {
    // ...
};

アロケータを設計する前に、何がどのように割り当てられるかを確認する必要があります。の答えoperator newは「なんでも」と「とにかく」です。これらの質問に適切に答えることができない場合、アロケーターはおそらく大きな改善にはなりません。

于 2011-07-06T12:57:06.850 に答える
0

パフォーマンスが向上するシナリオを考えられますか?

多くの (1 秒あたり 10k+) 割り当てと割り当て解除を行うものは、割り当て/解放のたびに複雑なクエリを実行する必要がないことからメリットがありますが、グループへの割り当て/解放を遅らせることによる節約の合計がそれ以上である場合に限ります。グループを処理するのに必要です (基本的に、ユニットごとの節約でグループを頭上で償却する必要があります)。

連続したメモリを使用すると、ツリーのようなノード/ポインターベースの構造に役立ちます(ただし、ポイントまでのみ)。ただし、実際の利点は、計画されたものとは大幅に異なる (または存在しない!) 可能性があります。そのため、このようなカスタム システムを作成する道を歩むときは、コードをプロファイリングし、既にそれがどのように使用されるかを念頭に置いてください(つまり、割り当てがほとんどないため、速度の向上がまったく問題にならないものに対してカスタムプールアロケーターを作成しても意味がありません)。

ただし、このようなものはデバッグに便利です。リークや上書き/無効な書き込みを監視するためのメモリにタグを付けるための優れたインターフェイスがあるため、標準システムと同じパフォーマンスであっても、他の方法で得ることができます.

于 2011-07-06T13:13:24.027 に答える