web-dev-qa-db-ja.com

初期化されていないストレージを持つSTLベクトル?

structsを連続したストレージに配置する必要がある内部ループを書いています。これらのstructsが事前にいくつあるかわかりません。私の問題は、STLのvectorがその値を0に初期化することです。そのため、私が何をしても、初期化のコストとstructのメンバーをそれらの値に設定するコストが発生します。

初期化を防ぐ方法はありますか、またはサイズ変更可能な連続したストレージと初期化されていない要素を持つSTLのようなコンテナーがありますか?

(コードのこの部分を最適化する必要があることは確かです。また、初期化にはかなりのコストがかかると確信しています。)

また、初期化がいつ発生するかについては、以下の私のコメントを参照してください。

一部のコード:

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size()
    memberVector.resize(mvSize + count); // causes 0-initialization

    for (int i = 0; i < count; ++i) {
        memberVector[mvSize + i].d1 = data1[i];
        memberVector[mvSize + i].d2 = data2[i];
    }
}
43
Jim Hunziker

_std::vector_は何らかの方法で配列の値を初期化する必要があります。つまり、いくつかのコンストラクター(またはコピーコンストラクター)を呼び出す必要があります。 vector(または任意のコンテナクラス)の動作は、配列の初期化されていないセクションに、初期化されているかのようにアクセスした場合、定義されていません。

最善の方法は、reserve()Push_back()を使用することです。これにより、コピーコンストラクタが使用され、デフォルトの構成が回避されます。

コード例を使用:

_struct YourData {
    int d1;
    int d2;
    YourData(int v1, int v2) : d1(v1), d2(v2) {}
};

std::vector<YourData> memberVector;

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size();

    // Does not initialize the extra elements
    memberVector.reserve(mvSize + count);

    // Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
        // Copy construct using a temporary.
        memberVector.Push_back(YourData(data1[i], data2[i]));
    }
}
_

このようなreserve()(またはresize())の呼び出しの唯一の問題は、必要以上に頻繁にコピーコンストラクターを呼び出すことになる可能性があることです。配列の最終的なサイズについて適切な予測ができる場合は、最初にスペースをreserve()することをお勧めします。最終的なサイズがわからない場合でも、少なくともコピー数は平均して最小限になります。

現在のバージョンのC++では、一時的な値がスタック上に構築され、ベクターメモリにコピー構築され、最後に一時的な値が破棄されるため、内部ループは少し非効率的です。ただし、C++の次のバージョンにはR-Value参照(_T&&_)と呼ばれる機能があり、これが役立ちます。

_std::vector_によって提供されるインターフェースは、別のオプションを許可しません。これは、工場のようなクラスを使用してデフォルト以外の値を構成することです。このパターンがC++で実装されているように見える大まかな例を次に示します。

_template <typename T>
class my_vector_replacement {

    // ...

    template <typename F>
    my_vector::Push_back_using_factory(F factory) {
        // ... check size of array, and resize if needed.

        // Copy construct using placement new,
        new(arrayData+end) T(factory())
        end += sizeof(T);
    }

    char* arrayData;
    size_t end; // Of initialized data in arrayData
};

// One of many possible implementations
struct MyFactory {
    MyFactory(int* p1, int* p2) : d1(p1), d2(p2) {}
    YourData operator()() const {
        return YourData(*d1,*d2);
    }
    int* d1;
    int* d2;
};

void GetsCalledALot(int* data1, int* data2, int count) {
    // ... Still will need the same call to a reserve() type function.

    // Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
        // Copy construct using a factory
        memberVector.Push_back_using_factory(MyFactory(data1+i, data2+i));
    }
}
_

これを行うと、独自のベクタークラスを作成する必要があります。この場合、単純な例であるべきことも複雑になります。ただし、このようなファクトリ関数を使用する方がよい場合もあります。たとえば、挿入が他の値に対して条件付きであり、実際には必要でなくても、無条件に高価な一時変数を作成する必要がある場合などです。

25
Lloyd

C++ 11(およびboost)では、unique_ptrの配列バージョンを使用して、初期化されていない配列を割り当てることができます。これは、stlコンテナではありませんが、メモリ管理され、C++風であり、多くのアプリケーションに十分対応できます。

auto my_uninit_array = std::unique_ptr<mystruct[]>(new mystruct[count]);
9
goertzenator

C++ 0xは、一時要素を完全に取り除く新しいメンバー関数テンプレートemplace_backvector(可変長テンプレートと完全転送に依存)に追加します。

memberVector.emplace_back(data1[i], data2[i]);
9
fredoverflow

Reserve()の応答を明確にするには、reserve()をPush_back()と組み合わせて使用​​する必要があります。このように、デフォルトのコンストラクターは各要素ではなく、コピーコンストラクターに対して呼び出されます。スタック上に構造体を設定し、それをベクターにコピーするというペナルティは引き続き発生します。一方で、

vect.Push_back(MyStruct(fieldValue1, fieldValue2))

コンパイラーは、ベクターに属するメモリー内に新しいインスタンスを直接作成します。それはオプティマイザがどれだけ賢いかに依存します。確認するには、生成されたコードを確認する必要があります。

8
user3458

だからここに問題があります、サイズ変更は挿入を呼び出しています。これは、新しく追加された各要素のデフォルト構築要素からコピー構築を行っています。これを0のコストにするには、独自のデフォルトコンストラクターと独自のコピーコンストラクターを空の関数として記述する必要があります。これをコピーコンストラクタに対して行うと、非常に悪い考えとなります。これは、std :: vectorの内部再割り当てアルゴリズムが壊れるためです。

概要:std :: vectorでこれを行うことはできません。

4
Don Neufeld

エラー...

メソッドを試してください:

std::vector<T>::reserve(x)

これにより、初期化せずにxアイテムに十分なメモリを予約できます(ベクトルはまだ空です)。したがって、xを超えるまで、再割り当ては行われません。

2番目のポイントは、ベクトルが値をゼロに初期化しないことです。デバッグでコードをテストしていますか?

G ++での検証後、次のコード:

#include <iostream>
#include <vector>

struct MyStruct
{
   int m_iValue00 ;
   int m_iValue01 ;
} ;

int main()
{
   MyStruct aaa, bbb, ccc ;

   std::vector<MyStruct> aMyStruct ;

   aMyStruct.Push_back(aaa) ;
   aMyStruct.Push_back(bbb) ;
   aMyStruct.Push_back(ccc) ;

   aMyStruct.resize(6) ; // [EDIT] double the size

   for(std::vector<MyStruct>::size_type i = 0, iMax = aMyStruct.size(); i < iMax; ++i)
   {
      std::cout << "[" << i << "] : " << aMyStruct[i].m_iValue00 << ", " << aMyStruct[0].m_iValue01 << "\n" ;
   }

   return 0 ;
}

次の結果が得られます。

[0] : 134515780, -16121856
[1] : 134554052, -16121856
[2] : 134544501, -16121856
[3] : 0, -16121856
[4] : 0, -16121856
[5] : 0, -16121856

あなたが見た初期化はおそらく人工物でした。

[編集]サイズ変更に関するコメントの後、コードを変更してサイズ変更行を追加しました。サイズ変更は効果的にベクター内のオブジェクトのデフォルトコンストラクターを呼び出しますが、デフォルトコンストラクターが何もしない場合、何も初​​期化されません...私はそれでもアーティファクトであると信じています次のコード:

aMyStruct.Push_back(MyStruct()) ;
aMyStruct.Push_back(MyStruct()) ;
aMyStruct.Push_back(MyStruct()) ;

そう... :-/

[編集2] Arkadiyによってすでに提供されているように、解決策は、必要なパラメーターを取得するインラインコンストラクターを使用することです。何かのようなもの

struct MyStruct
{
   MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {}
   int d1, d2 ;
} ;

これはおそらくコードにインライン化されます。

ただし、とにかくプロファイラーでコードを調べて、このコードがアプリケーションのボトルネックであることを確認する必要があります。

3
paercebal

何もしないデフォルトコンストラクターを使用して、要素タイプの周りにラッパータイプを使用できます。例えば。:

template <typename T>
struct no_init
{
    T value;

    no_init() { static_assert(std::is_standard_layout<no_init<T>>::value && sizeof(T) == sizeof(no_init<T>), "T does not have standard layout"); }

    no_init(T& v) { value = v; }
    T& operator=(T& v) { value = v; return value; }

    no_init(no_init<T>& n) { value = n.value; }
    no_init(no_init<T>&& n) { value = std::move(n.value); }
    T& operator=(no_init<T>& n) { value = n.value; return this; }
    T& operator=(no_init<T>&& n) { value = std::move(n.value); return this; }

    T* operator&() { return &value; } // So you can use &(vec[0]) etc.
};

使用するには:

std::vector<no_init<char>> vec;
vec.resize(2ul * 1024ul * 1024ul * 1024ul);
2
deonb

あなたのコードから、それはあなたがそれぞれ2つのintから成る構造体のベクトルを持っているように見えます。代わりに、intの2つのベクトルを使用できますか?その後

copy(data1, data1 + count, back_inserter(v1));
copy(data2, data2 + count, back_inserter(v2));

今、あなたは毎回構造体をコピーするために支払う必要はありません。

1
fizzer

Std :: vector :: reserve()メソッドを使用します。ベクトルのサイズは変更されませんが、スペースが割り当てられます。

1
nsanders

あなたのコメントから他のポスターへ、あなたはmalloc()と友達が残っているようです。ベクターでは、構成されていない要素を使用できません。

1
fizzer

要素を初期化せずに主張し、front()、back()、Push_back()などのいくつかのメソッドを犠牲にする場合は、numericのboostベクトルを使用します。 resize()を呼び出すときに既存の要素を保持しないようにすることもできます...

1
Darko Veberic

STLはあなたの答えではないと思います。 realloc()を使用して独自のソリューションをロールする必要があります。ポインタと要素のサイズまたは数のいずれかを格納し、それを使用して、realloc()の後に要素の追加を開始する場所を見つける必要があります。

int *memberArray;
int arrayCount;
void GetsCalledALot(int* data1, int* data2, int count) {
    memberArray = realloc(memberArray, sizeof(int) * (arrayCount + count);
    for (int i = 0; i < count; ++i) {
        memberArray[arrayCount + i].d1 = data1[i];
        memberArray[arrayCount + i].d2 = data2[i];
    }
    arrayCount += count;
}
0
mbyrne215

私は次のようなことをします:

void GetsCalledALot(int* data1, int* data2, int count)
{
  const size_t mvSize = memberVector.size();
  memberVector.reserve(mvSize + count);

  for (int i = 0; i < count; ++i) {
    memberVector.Push_back(MyType(data1[i], data2[i]));
  }
}

MemberVectorに格納されているタイプのctorを定義する必要がありますが、両方の世界で最高の機能を提供するため、これはわずかなコストです。不要な初期化は行われず、ループ中に再割り当ては行われません。

0

構造体自体が連続したメモリにある必要がありますか、それともstruct *のベクトルを持つことで回避できますか?

ベクトルは、追加したもののコピーを作成するため、オブジェクトではなくポインターのベクトルを使用することは、パフォーマンスを向上させる1つの方法です。

0
17 of 26