51

struct連続したストレージに sを配置する必要がある内部ループを作成しています。structこれらの s がいくつになるかは、事前にわかりません。私の問題は、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];
    }
}
4

16 に答える 16

29

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

最善の方法は、 and を使用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()、最初にスペースを1回使用することをお勧めします。ただし、最終的なサイズがわからない場合でも、少なくともコピーの数は平均して最小限になります.

現在のバージョンの 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));
    }
}

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

于 2008-09-18T22:12:49.817 に答える
11

emplace_backC ++ 0xは、新しいメンバー関数テンプレートを追加しますvector(可変個引数テンプレートと完全な転送に依存します)。これにより、一時的なものが完全に削除されます。

memberVector.emplace_back(data1[i], data2[i]);
于 2010-05-09T18:48:14.380 に答える
8

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

vect.push_back(MyStruct(fieldValue1, fieldValue2))

コンパイラは、ベクトルに属するメモリ内に新しいインスタンスを直接構築します。それは、オプティマイザーがどれほど賢いかによって異なります。確認するには、生成されたコードを確認する必要があります。

于 2008-09-18T20:56:31.480 に答える
6

新しい要素boost::noinit_adaptorデフォルトで初期化するために使用できます(これは組み込み型の初期化ではありません):

std::vector<T, boost::noinit_adaptor<std::allocator<T>> memberVector;

にイニシャライザを渡さない限り、resizeデフォルト新しい要素が初期化されます。

于 2020-05-06T17:44:33.847 に答える
5

したがって、ここで問題が発生します。resizeはinsertを呼び出します。これは、新しく追加された要素ごとに、デフォルトで構築された要素からコピー構築を実行します。これを0のコストにするには、独自のデフォルトコンストラクターと独自のコピーコンストラクターを空の関数として作成する必要があります。コピーコンストラクターに対してこれを行うと、std :: vectorの内部再割り当てアルゴリズムが機能しなくなるため、非常に悪い考えです。

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

于 2008-09-18T20:55:08.507 に答える
4

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

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);
于 2017-02-28T13:52:53.733 に答える
3

エラー...

次の方法を試してください。

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 ;
} ;

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

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

于 2008-09-18T20:34:49.723 に答える
1

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

于 2008-09-18T20:32:47.700 に答える
1

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

于 2008-09-18T20:57:42.680 に答える
1

どうしても要素を初期化せず、front()、back()、push_back() などのメソッドを犠牲にする場合は、numeric のブースト ベクトルを使用します。resize() を呼び出すときに既存の要素を保持しないことさえできます...

于 2011-09-07T16:56:28.920 に答える
1

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

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

これで、毎回構造体をコピーする費用がかかりません。

于 2008-09-18T21:15:16.413 に答える
0

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;
}
于 2008-09-18T20:53:25.623 に答える
0

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

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

于 2008-09-18T20:44:24.677 に答える
0

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

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 を定義する必要がありますが、両方の利点を最大限に活用できるため、コストはわずかです。不必要な初期化は行われず、ループ中に再割り当ては発生しません。

于 2008-09-18T21:59:47.693 に答える