9

std :: unique_ptrs(特にMSFT VS 10.0の実装)に問題があります。それらのstd::listを作成するとき、基礎となるオブジェクトだけのstd :: listを作成するときの2倍のメモリを使用します(注:これは大きなオブジェクトです-〜200バイトなので、単なる周りにある追加の参照カウンター)。

言い換えれば、私が実行した場合:

std::list<MyObj> X;
X.resize( 1000, MyObj());

私のアプリケーションは、実行時の半分のメモリを必要とします。

std::list<std::unique_ptr<MyObj>> X;
for ( int i=0; i<1000; i++ ) X.push_back(std::unique_ptr<MyObj>(new MyObj()));

MSFTの実装を確認しましたが、明らかなものは何もありません。誰かがこれに遭遇し、何かアイデアがありますか?

編集: わかりました、もう少し明確/具体的にします。これは明らかにWindowsのメモリ使用量の問題であり、明らかに何かが欠けています。私は今、次のことを試しました:

  1. std::list100000MyObjのを作成します
  2. std::list100000MyObj*のを作成します
  3. std::list100000int*のを作成します
  4. std::list50000int*のを作成します

いずれの場合も、リストの各追加メンバーは、ポインターであろうとなかろうと、アプリケーションを4400(!)バイト膨らませています。これはリリースの64ビットビルドであり、デバッグ情報は含まれていませ([リンカー]>[デバッグ]>[デバッグ情報の生成]が[いいえ]に設定されています)。

明らかに、これをもう少し調査して、より小さなテストケースに絞り込む必要があります。

興味のある方のために、ProcessExplorerを使用してアプリケーションのサイズを決定しています

それは完全にヒープの断片化であったことが判明しました。なんてばかげている。8バイトのオブジェクトあたり4400バイト!事前割り当てに切り替えたところ、問題は完全に解消されました。オブジェクトごとの割り当てに依存することの非効率性に慣れていますが、これはばかげたことです。

以下のMyObjの実装:

class   MyObj
{
public:
    MyObj() { memset(this,0,sizeof(MyObj)); }

    double              m_1;
    double              m_2;
    double              m_3;
    double              m_4;
    double              m_5;
    double              m_6;
    double              m_7;
    double              m_8;
    double              m_9;
    double              m_10;
    double              m_11;           
    double              m_12;           
    double              m_13;
    double              m_14;
    double              m_15;
    double              m_16;
    double              m_17;
    double              m_18;
    double              m_19;
    double              m_20;
    double              m_21;
    double              m_22;
    double              m_23;
    CUnit*              m_UnitPtr;
    CUnitPos*           m_UnitPosPtr;
};
4

3 に答える 3

3

追加されたメモリは、ヒープの非効率性に起因する可能性があります。内部の断片化と malloc データのために、割り当てるブロックごとに追加料金を支払う必要があります。ペナルティ ヒットが発生する割り当ての 2 倍の量を実行しています。

たとえば、これは次のとおりです。

for(int i = 0; i < 100; ++i) {
  new int;
}

これより多くのメモリを使用します:

new int[100];

配る金額は同じなのに。


編集:

Linux で GCC を使用して unique_ptr を使用すると、使用されるメモリが約 13% 増えています。

于 2011-11-28T16:39:27.223 に答える
2

std::list<MyObj>オブジェクトの N 個のコピー (+ リストのポインターに必要な情報) が含まれます。

std::unique_ptr<MyObj>オブジェクトのインスタンスへのポインタが含まれています。( のみを含める必要がありますMyObj*)。

したがって、 astd::list<std::unique_ptr<MyObj>>は最初のリストと直接同等ではありません。std::list<MyObj*>リストと同じサイズにする必要がありstd::unque_ptrます。

実装を検証した後、オブジェクト自体のポインターの隣に埋め込むことができる唯一のものは「deleter」である可能性があります。これは、デフォルトの場合、 を呼び出す空のオブジェクトですoperator delete

デバッグ ビルドまたはリリース ビルドはありますか?

于 2011-11-28T16:37:01.523 に答える
2

これは答えではありませんが、コメントに収まらず、説明になる可能性があります。

主張を再現できません (GCC 4.6.2)。次のコードを使用します。

#include <memory>
#include <list>

struct Foo { char p[200]; };

int main()
{
  //std::list<Foo> l1(100);

  std::list<std::unique_ptr<Foo>> l2;
  for (unsigned int i = 0; i != 100; ++i) l2.emplace_back(new Foo);
}

有効にするだけでl1生成されます (Valgrind で):

total heap usage: 100 allocs, 100 frees, 20,800 bytes allocated

有効にするだけl2で、ループは次のようになります。

total heap usage: 200 allocs, 200 frees, 21,200 bytes allocated

スマート ポインターはちょうど 4 × 100 バイトを占有します。

どちらの場合も、次のようになり/usr/bin/time -vます。

Maximum resident set size (kbytes): 3136

さらにpmap、両方のケースで次のように表示されますtotal 2996K。確認するために、オブジェクト サイズを 20000 に、要素数を 10000 に変更しました。数値は198404Kvs198484Kです。正確には 80000B の差、一意のポインターごとに 8B (おそらく、リストのアロケーターで 8B のアライメントが行われています)。同じ変更の下で、 によって報告される「最大常駐セット サイズ」time -v162768対になり164304ました。

于 2011-11-28T16:40:45.143 に答える