C ++標準ライブラリの実装を比較/比較するドキュメントはありますか?[閉まっている]


16

(これはゲームプログラミングそのものではありませんが、歴史上、すべての大規模なゲームがこれらのことを心配していると言っていても、これを早めに最適化しないように言われると確信しています)

さまざまなC ++標準ライブラリ実装間のパフォーマンス、特にメモリ使用量の違いをまとめたドキュメントはどこにありますか?一部の実装の詳細はNDAによって保護されていますが、STLportとlibstdc ++とlibc ++とMSVC / Dinkumware(vs. EASTL?)との比較は非常に役立つようです。

特に、次のような質問に対する答えを探しています。

  • どのくらいのメモリオーバーヘッドが標準コンテナに関連付けられていますか?
  • 仮に宣言されただけで動的割り当てを行うコンテナがある場合、どのコンテナですか?
  • std :: stringはコピーオンライトを行いますか?短い文字列の最適化?ロープ?
  • std :: dequeはリングバッファーを使用しますか?

私はdeque、ベクターを使用してSTLに常に実装されているという印象を受けていました。
テトラッド

@Tetrad:数週間前まで私もそうでしたが、それがロープのような構造で実装されていることをよく読みました。それがSTLportにあるようです。

STLには、さまざまなデータ構造(シーケンシャルおよびアソシエイティブの両方)、アルゴリズム、および実装されたヘルパークラスに関する情報を見つけるために使用できるオープンなワーキングドラフトがあります。ただし、メモリオーバーヘッドは、仕様が定義されているのではなく、実装固有のものであるようです。
トーマス・ラッセル

3
@Duck:ゲーム開発は、低レベルの非仮想メモリシステムで実行されるため、メモリの割り当てを細かく追跡する必要があるが、高レベルのC ++機能を定期的に使用することを知っている唯一の場所です。SOに関するすべての回答は、「時期尚早に最適化しないでください。STLは問題ありません。使用してください!」-ここまでの回答の50%はそれです-それでも、Maikのテストは明らかに、std :: mapを使用したいゲームと、一般的なstd :: deque実装に関するTetradの混乱と私の懸念を示しています。

2
@Joe Wreschnigこの結果に興味があるので、本当に投票したくありません。:p
共産主義者のダック

回答:


6

このような比較チャートが見つからない場合、代替手段は、問題のSTLクラスに独自のアロケーターを挿入し、ログを追加することです。

私がテストした実装(VC 8.0)は、string / vector / dequeを宣言するだけでメモリ割り当てを使用しませんが、リストとマップを行います。3文字を追加しても割り当てがトリガーされないため、文字列には短い文字列最適化があります。コードの下に出力が追加されます。

// basic allocator implementation used from here
// http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c4079

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <map>

template <class T> class my_allocator;

// specialize for void:
template <> 
class my_allocator<void> 
{
public:
    typedef void*       pointer;
    typedef const void* const_pointer;
    // reference to void members are impossible.
    typedef void value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };
};

#define LOG_ALLOC_SIZE(call, size)      std::cout << "  " << call << "  " << std::setw(2) << size << " byte" << std::endl

template <class T> 
class my_allocator 
{
public:
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;
    typedef T         value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };

    my_allocator() throw() : alloc() {}
    my_allocator(const my_allocator&b) throw() : alloc(b.alloc) {}

    template <class U> my_allocator(const my_allocator<U>&b) throw() : alloc(b.alloc) {}
    ~my_allocator() throw() {}

    pointer       address(reference x) const                    { return alloc.address(x); }
    const_pointer address(const_reference x) const              { return alloc.address(x); }

    pointer allocate(size_type s, 
               my_allocator<void>::const_pointer hint = 0)      { LOG_ALLOC_SIZE("my_allocator::allocate  ", s * sizeof(T)); return alloc.allocate(s, hint); }
    void deallocate(pointer p, size_type n)                     { LOG_ALLOC_SIZE("my_allocator::deallocate", n * sizeof(T)); alloc.deallocate(p, n); }

    size_type max_size() const throw()                          { return alloc.max_size(); }

    void construct(pointer p, const T& val)                     { alloc.construct(p, val); }
    void destroy(pointer p)                                     { alloc.destroy(p); }

    std::allocator<T> alloc;
};

int main(int argc, char *argv[])
{

    {
        typedef std::basic_string<char, std::char_traits<char>, my_allocator<char> > my_string;

        std::cout << "===============================================" << std::endl;
        std::cout << "my_string ctor start" << std::endl;
        my_string test;
        std::cout << "my_string ctor end" << std::endl;
        std::cout << "my_string add 3 chars" << std::endl;
        test = "abc";
        std::cout << "my_string add a huge number of chars chars" << std::endl;
        test += "d df uodfug ondusgp idugnösndögs ifdögsdoiug ösodifugnösdiuödofu odsugöodiu niu od unoudö n nodsu nosfdi un abc";
        std::cout << "my_string copy" << std::endl;
        my_string copy = test;
        std::cout << "my_string copy on write test" << std::endl;
        copy[3] = 'X';
        std::cout << "my_string dtors start" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "vector ctor start" << std::endl;
        std::vector<int, my_allocator<int> > v;
        std::cout << "vector ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            v.push_back(i);
        }
        std::cout << "vector dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "deque ctor start" << std::endl;
        std::deque<int, my_allocator<int> > d;
        std::cout << "deque ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "deque insert start" << std::endl;
            d.push_back(i);
            std::cout << "deque insert end" << std::endl;
        }
        std::cout << "deque dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "list ctor start" << std::endl;
        std::list<int, my_allocator<int> > l;
        std::cout << "list ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "list insert start" << std::endl;
            l.push_back(i);
            std::cout << "list insert end" << std::endl;
        }
        std::cout << "list dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "map ctor start" << std::endl;
        std::map<int, float, std::less<int>, my_allocator<std::pair<const int, float> > > m;
        std::cout << "map ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "map insert start" << std::endl;
            std::pair<int, float> a(i, (float)i);
            m.insert(a);
            std::cout << "map insert end" << std::endl;
        }
        std::cout << "map dtor starts" << std::endl;
    }

    return 0;
}

これまでにテストされたVC8とSTLPort 5.2は、ここに比較があります(テストに含まれる:文字列、ベクトル、両端キュー、リスト、マップ)

                    Allocation on declare   Overhead List Node      Overhead Map Node

VC8                 map, list               8 Byte                  16 Byte
STLPort 5.2 (VC8)   deque                   8 Byte                  16 Byte
Paulhodge's EASTL   (none)                  8 Byte                  16 Byte

VC8出力文字列/ベクトル/ deque / list / map:

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    128 byte
my_string copy
  my_allocator::allocate    128 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  128 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    12 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate  12 byte
  my_allocator::allocate    24 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  24 byte

===============================================
deque ctor start
deque ctor end
deque insert start
  my_allocator::allocate    32 byte
  my_allocator::allocate    16 byte
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
  my_allocator::allocate    16 byte
deque insert end
deque dtor starts
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
  my_allocator::allocate    12 byte
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
  my_allocator::allocate    24 byte
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

STLPort 5.2。VC8でコンパイルされた出力

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::deallocate   0 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
deque ctor start
  my_allocator::allocate    32 byte
  my_allocator::allocate    128 byte
deque ctor end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque dtor starts
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

EASTLの結果、dequeは使用不可

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
  my_allocator::allocate     9 byte
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
  my_allocator::deallocate   9 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

これは、基になる割り当ての詳細を取得するのに役立ちますが、残念ながらオーバーヘッドと予想されるキャッシュパフォーマンスについては何もわかりません。

@Joe正しい、すべての質問に答えるのは難しい。あなたが「オーバーヘッド」で何を意味しているのか正確にはわかりません。私はオーバーヘッドによってメモリ消費を意味すると考えました。
マイクセンダー

「オーバーヘッド」とは、構造の空のインスタンスと関連するすべてのイテレータのサイズを大きくし、より複雑なインスタンスが割り当てを処理する方法を意味します。各ノードの基本割り当てコストなどを支払いますか?

1
問題は、「この比較を行ってください」ということではなく、「この比較のリソースはどこにあるか」ということではありません。おそらく、あなたはそれをGoogleのサイトやウィキなどに投げ入れ始めるべきでしょう。

1
@Joeは今ここにいます:p別のサイトに移動することにあまり興味がありません。結果に興味がありました。
マイクセンダー

8

std::string書き込み時にコピーを行いません。CoWは以前は最適化でしたが、複数のスレッドが状況を把握するとすぐに悲観的ではありません。大量の要因によってコードが遅くなる可能性があります。とても悪いので、C ++ 0x標準は実装戦略として積極的に禁止しています。それだけでなく、std::string可変イテレータと文字参照を使いこなすことの許容性は、「書き込み」std::stringがほぼすべての操作を伴うことを意味します。

短い文字列の最適化は、約6文字、またはその地域の何かだと思います。ロープは許可されていstd::stringませんc_str()。関数の連続したメモリを保存する必要があります。技術的には、同じクラスで連続したストリングとロープの両方を維持できますが、誰もそれをしませんでした。さらに、私がロープについて知っていることから、ロープを操作してもスレッドセーフにするのは信じられないほど遅くなります。おそらくCoWと同じくらい悪いか悪いです。

最新のSTLで宣言されることにより、メモリ割り当てを行うコンテナはありません。リストやマップなどのノードベースのコンテナは、これを行うために使用されていましたが、現在ではエンド最適化が組み込まれており、それを必要としません。空のコンテナと交換する「スワップ最適化」と呼ばれる最適化を実行するのが一般的です。考慮してください:

std::vector<std::string> MahFunction();
int main() {
    std::vector<std::string> MahVariable;
    MahFunction().swap(MahVariable);
}

もちろん、C ++ 0xではこれは冗長ですが、C ++ 03ではこれが一般的に使用されたときに、MahVariableが宣言時にメモリを割り当てると、効果が低下します。これはvector、MSVC9 STLのように、要素をコピーする必要がなくなったコンテナのより高速な再割り当てに使用されたという事実を知っています。

deque展開されたリンクリストと呼ばれるものを使用します。これは基本的に配列のリストであり、通常はノード内の固定サイズです。そのため、ほとんどの用途のために、それは両方のデータstructures-の連続アクセスおよび償却O(1)除去し、前面と背面の両方に追加することができるという利点を保持し、より優れたイテレータの無効化をvectordequeアルゴリズムの複雑さとイテレータの無効化の保証のため、ベクトルによって実装することはできません。

どのくらいのメモリオーバーヘッドが関連付けられていますか?まあ、正直なところ、それは質問する価値のない質問です。STLコンテナは効率的に設計されており、その機能を複製しようとすると、パフォーマンスが低下したり、同じ場所に戻ったりすることになります。それらの基礎となるデータ構造を知ることで、それらが使用、提供、または取得するメモリオーバーヘッドを知ることができます。それは、小さな文字列の最適化などの正当な理由でそれ以上になります。


「C ++ 0x標準が実装戦略として積極的に禁止しているのは非常に悪いことです。」そして、以前の実装がそれを使用したか、使用しようとしたため、彼らはそれを禁止します。皆さんは、常に最適に実装された最新のSTLを常に使用している世界に住んでいるようです。この答えはまったく役に立ちません。

また、std :: dequeのどのプロパティが連続する基礎となるストレージを防ぐと思うか興味があります-反復子は、ベクトルで簡単に実行できる中間または挿入の後ではなく、開始/終了での削除後にのみ有効です。そして、循環バッファを使用することは、すべてのアルゴリズムの保証を満たしているようです-償却O(1)挿入と削除は両端で、O(n)は中間で削除します。

3
@ジョー:CoWは90年代後半から悪いことで知られていると思います。CStringを使用した文字列の実装がありますが、特にCStringがありますが、それstd::stringは当時のことを意味しません。そのために、最新かつ最高のSTL実装を使用する必要はありません。msdn.microsoft.com/en-us/library/22a9t119.aspxは、「要素が先頭に挿入された場合、すべての参照が有効なままである」と述べています。循環バッファでそれをどのように実装するつもりなのかわかりません。満杯になったらサイズを変更する必要があるからです。
-DeadMG


実装手法としてCOWを擁護するつもりはありませんが、ソフトウェアが低品質であると特定された後も、低品質の技術を使用して実装され続ける頻度については、私は素朴ではありません。たとえば、上記のMaikのテストは、宣言時に割り当てを行う最新のstdlibを明らかにしています。両端キュー参照の有効性に関するポインタをお寄せいただきありがとうございます。(簡単に言えば、ベクターはイテレーターの無効化とアルゴリズムの複雑さに関するすべての保証を満たすことができます。その要件はどちらでもありません。)どちらかといえば、これは私の質問が求める文書のさらなる必要性と考えています。

2

質問は、「この比較を行ってください」ではなく、「この比較のためのリソースはどこですか」ということではありません

それが本当にあなたの質問である場合(実際にあなたが実際の質問テキストで言ったものではなく、4つの質問で終わり、どれもリソースを見つけることができるかを尋ねていません)、答えは単純です:

ありません。

C ++プログラマの大半は、標準ライブラリの構造のオーバーヘッド、(あるそれらのキャッシュのパフォーマンスについてはそれほど気にする必要はありません非常にコンパイラに依存とにかく)、またはそういったことを。言うまでもなく、通常、標準ライブラリの実装を選択することはできません。コンパイラに付属しているものを使用します。そのため、たとえ不愉快なことをしたとしても、選択肢の選択肢は限られています。

もちろん、この種のことを気にするプログラマーがいます。しかし、彼らは皆、昔から標準ライブラリを使用して誓っていました。

したがって、単に気にしないプログラマのグループが1つあります。そして、それを使用しているかどうか気にするプログラマの別のグループですが、使用していないので気にしません。誰も気にしないので、この種のことに関する本当の情報はありません。情報の非公式のパッチはあちこちにあります(効果的なC ++にはstd :: string実装とそれらの間の大きな違いに関するセクションがあります)が、包括的なものはありません。そして、確かに最新のものはありませんでした。


投機的な答え。おそらく本当の場合は+1、それを証明する方法がない場合は-1。
減速

私は過去に多くの非常に優れた詳細な比較を見てきましたが、それらはすべて時代遅れです。移動の導入前のすべては、今日ではほとんど無関係です。
ピーター-アンバンロバートハーヴェイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.