2

以下のカスタムアロケータでベクトルを使用したいのですが、その中にconstruct()destroy()空の本文があります。

struct MyAllocator : public std::allocator<char> {
    typedef allocator<char> Alloc;
    //void destroy(Alloc::pointer p) {} // pre-c+11
    //void construct(Alloc::pointer p, Alloc::const_reference val) {} // pre-c++11
    template< class U > void destroy(U* p) {}
    template< class U, class... Args > void construct(U* p, Args&&... args) {}
    template<typename U> struct rebind {typedef MyAllocator other;};
};

別の質問で指定した理由によりvector、ループ内で数回サイズ変更する必要があります。パフォーマンスのテストを簡素化するために、次のような非常に単純なループを作成しました。

std::vector<char, MyAllocator> v;
v.reserve(1000000); // or more. Make sure there is always enough allocated memory
while (true) {
   v.resize(1000000);
   // sleep for 10 ms
   v.clear(); // or v.resize(0);
};

construct()アロケータに空のdestroy()メンバー関数があるにもかかわらず、CPU消費量が30%から80%に増加するようにサイズを変更することに気づきました。そのため、パフォーマンスへの影響はごくわずかであるか、まったく影響がない(最適化が有効になっている)と予想していました。その消費の増加はどのように可能ですか?2番目の質問は、サイズ変更後にメモリを読み取るときに、サイズ変更されたメモリ内の各文字の値が0であることがわかる理由です(constuct()何もしないため、ゼロ以外の値が予想されます)。

私の環境はg++4.7.0で、-O3レベルの最適化が有効になっています。PC Intelデュアルコア、4GBの空きメモリ。どうやらへの呼び出しconstructはまったく最適化できませんでしたか?

4

2 に答える 2

2

更新しました

これは完全な書き直しです。元の投稿/私の回答にエラーがあり、同じアロケータを2回ベンチマークしました。おっと。

さて、パフォーマンスに大きな違いが見られます。私は次のテストベッドを作成しました。これは、重要なものが完全に最適化されていないことを確認するためにいくつかの予防措置を講じています。次に、(-O0 -fno-inlineを使用して)アロケータconstructdestruct呼び出しが予想される回数だけ呼び出されることを確認しました(はい)。

#include <vector>
#include <cstdlib>

template<typename T>
struct MyAllocator : public std::allocator<T> {
    typedef std::allocator<T> Alloc;
    //void destroy(Alloc::pointer p) {} // pre-c+11
    //void construct(Alloc::pointer p, Alloc::const_reference val) {} // pre-c++11
    template< class U > void destroy(U* p) {}
    template< class U, class... Args > void construct(U* p, Args&&... args) {}
    template<typename U> struct rebind {typedef MyAllocator other;};
};

int main()
{
    typedef char T;
#ifdef OWN_ALLOCATOR
    std::vector<T, MyAllocator<T> > v;
#else
    std::vector<T> v;
#endif
    volatile unsigned long long x = 0;
    v.reserve(1000000); // or more. Make sure there is always enough allocated memory
    for(auto i=0ul; i< 1<<18; i++) {
        v.resize(1000000);
        x += v[rand()%v.size()];//._x;
        v.clear(); // or v.resize(0);
    };
}

タイミングの違いは次のようにマークされています。

g++ -g -O3 -std=c++0x -I ~/custom/boost/ test.cpp -o test 

real    0m9.300s
user    0m9.289s
sys 0m0.000s

g++ -g -O3 -std=c++0x -DOWN_ALLOCATOR -I ~/custom/boost/ test.cpp -o test 

real    0m0.004s
user    0m0.000s
sys 0m0.000s

char私はあなたが見ているものが(それはPODタイプである)アロケータ操作を最適化する標準ライブラリに関連していると推測することができるだけです。

使用するとタイミングがさらにずれます

struct NonTrivial
{
    NonTrivial() { _x = 42; }
    virtual ~NonTrivial() {}
    char _x;
};

typedef NonTrivial T;

この場合、デフォルトのアロケータは2分以上かかります(まだ実行中です)。一方、「ダミー」のMyAllocatorは約0.006秒を費やします。(これにより、適切に初期化されていない要素を参照する未定義の動作が呼び出されることに注意してください。)

于 2013-03-06T02:13:34.037 に答える
0

(以下のGManNickGとJonathan Wakelyのおかげで修正されました)

C ++ 11では、 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3346.pdfで提案されているポストスタンダードの修正resize()により、カスタムアロケータを使用して追加された要素を構築します。

以前のバージョンでは、resize()valueは追加された要素を初期化しますが、これには時間がかかります。

これらの初期化手順は、メモリの割り当てとは関係ありません。割り当てられた後にメモリに対して行われることです。価値の初期化は避けられない費用です。

現在のコンパイラでのC++11標準への準拠の状態を考えると、どのアプローチが使用されているかを確認するためにヘッダーを調べる価値があります。

値の初期化は、不必要で不便な場合もありましたが、多くのプログラムを意図しないミスから保護していました。たとえば、std::vector<std::string>100個の「初期化されていない」文字列を持つようにサイズを変更し、それらから読み取る前に代入を開始できると考える人がいるかもしれませんが、代入演算子の前提条件は、変更されるオブジェクトが適切に構築されていることです...それ以外の場合はおそらくガベージポインタを見つけてdelete[]それを試してみます。new各要素を注意深く配置することによってのみ、それらを安全に構築できます。APIの設計は、堅牢性の面で誤りがあります。

于 2013-03-06T01:48:59.003 に答える