Vetores de STL com armazenamento não inicializado?

Estou escrevendo um laço interno que precisa colocar struct s no armazenamento contíguo. Eu não sei quantos destes struct s haverá antes do tempo. Meu problema é que o vetor do STL inicializa seus valores para 0, então não importa o que eu faça, eu incorpo no custo da inicialização mais o custo de configurar os membros do struct aos seus valores.

Existe alguma maneira de impedir a inicialização ou existe um contêiner semelhante ao STL com armazenamento contíguo redimensionável e elementos não inicializados?

(Tenho certeza de que essa parte do código precisa ser otimizada e tenho certeza de que a inicialização é um custo significativo.)

Além disso, veja meus comentários abaixo para um esclarecimento sobre quando a inicialização ocorre.

ALGUM CÓDIGO:

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];
    }
}
0
adicionado editado
Visualizações: 2
Outro esclarecimento: não é que o construtor inicialize os valores para 0. É que as chamadas de redimensionamento inserem, o que faz.
adicionado o autor Jim Hunziker, fonte
Nota - usar reserve() não é uma solução, pois você não pode acessar legalmente os dados que estão nos locais end() e acima.
adicionado o autor Jim Hunziker, fonte
OBSERVAÇÃO: você não pode acessar dados não inicializados de qualquer maneira. Mesmo problema para o vetor passado .end() e membros não inicializados de um T []. Mas com um vetor, é provável que o código de depuração lhe diga isso agora. O código da matriz falhará silenciosamente no PC do cliente.
adicionado o autor MSalters, fonte
Veja também stackoverflow.com/q/7218574/1969455 para uma abordagem de alocador. (bom para tipos POD)
adicionado o autor Matthäus Brandl, fonte
Você poderia nos dar a declaração struct também? Obrigado... :-)
adicionado o autor paercebal, fonte
Essa é uma boa pergunta. Para algumas aplicações, é importante perceber que o std :: vector sempre inicializa seus elementos, mesmo que sejam POD (plain-old-data data).
adicionado o autor nobar, fonte

14 Respostas

Então aqui está o problema, resize está chamando insert, que está fazendo uma construção de cópia a partir de um elemento construído padrão para cada um dos elementos recém-adicionados. Para obter esse custo para 0, você precisa escrever seu próprio construtor padrão E seu próprio construtor de cópias como funções vazias. Fazer isso com o seu construtor de cópia é uma idéia muito ruim porque ele quebrará os algoritmos de realocação interna do std :: vector.

Resumo: Você não poderá fazer isso com std :: vector.

0
adicionado
Essa é a verdadeira questão. std :: vector deve perceber que não precisa fazer nenhuma inicialização se T tiver um construtor padrão trivial. Obrigado por apontar que o construtor de cópia é o que está fazendo o trabalho desnecessário aqui.
adicionado o autor Eric Hein, fonte

Para esclarecer as respostas de reserve (): você precisa usar reserve() em conjunto com push_back (). Desta forma, o construtor padrão não é chamado para cada elemento, mas sim o construtor de cópia. Você ainda incorre na penalidade de configurar sua estrutura na pilha e depois copiá-la para o vetor. Por outro lado, é possível que se você usar

vect.push_back(MyStruct(fieldValue1, fieldValue2))

o compilador construirá a nova instância diretamente na memória que pertence ao vetor. Depende de quão inteligente é o otimizador. Você precisa verificar o código gerado para descobrir.

0
adicionado
Acontece que o otimizador do gcc, no nível O3, não é inteligente o suficiente para evitar a cópia.
adicionado o autor Jim Hunziker, fonte

O C ++ 0x adiciona um novo modelo de função de membro emplace_back ao vetor (que depende de modelos variadic e encaminhamento perfeito) que se livra de qualquer temporário inteiramente:

memberVector.emplace_back(data1[i], data2[i]);
0
adicionado

Em C ++ 11 (e boost) você pode usar a versão de array de unique_ptr para alocar uma matriz não inicializada. Este não é um contêiner stl, mas ainda é gerenciado pela memória e C ++ - ish, que será bom o suficiente para muitos aplicativos.

auto my_uninit_array = std::unique_ptr(new mystruct[count]);
0
adicionado

Use o método std :: vector :: reserve (). Não irá redimensionar o vetor, mas alocará o espaço.

0
adicionado

Errar...

tente o método:

std::vector::reserve(x)

Ele permitirá que você reserve memória suficiente para itens x sem inicializar nenhum (seu vetor ainda está vazio). Assim, não haverá realocação até passar por x.

O segundo ponto é que o vetor não inicializa os valores para zero. Você está testando seu código no debug?

Após verificação em g ++, o seguinte código:

#include 
#include 

struct MyStruct
{
   int m_iValue00 ;
   int m_iValue01 ;
} ;

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

   std::vector aMyStruct ;

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

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

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

   return 0 ;
}

fornece os seguintes resultados:

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

A inicialização que você viu provavelmente foi um artefato.

[EDITAR] Após o comentário sobre o redimensionamento, modifiquei o código para adicionar a linha de redimensionamento. O redimensionamento efetivamente chama o construtor padrão do objeto dentro do vetor, mas se o construtor padrão não fizer nada, então nada é inicializado ... Eu ainda acredito que foi um artefato (eu consegui pela primeira vez ter o vetor todo zerado com o seguinte código:

aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;

Assim... : - /

[EDIT 2] Como já foi oferecido pelo Arkadiy, a solução é usar um construtor embutido tomando os parâmetros desejados. Algo como

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

Isso provavelmente ficará embutido no seu código.

Mas, de qualquer forma, você deve estudar seu código com um criador de perfil para ter certeza de que esse código é o gargalo do seu aplicativo.

0
adicionado
Eu escrevi uma nota acima. Não é o construtor de vetor que inicializa para 0. Ele é redimensionado ().
adicionado o autor Jim Hunziker, fonte
Eu acho que você está no caminho certo. Eu não tenho nenhum construtor definido na estrutura, portanto, seu construtor padrão (acredito) inicializa zero. Vou verificar se adicionar um construtor padrão que não faz nada resolve o problema.
adicionado o autor Jim Hunziker, fonte
Greg Rogers está certo. Meu palpite é que a memória era "zero" por causa de alguma inicialização de processo independente do código que escrevi. Em C ++, você não paga por algo que não usa. Então, se você estiver escrevendo código semelhante ao C, não deverá ter sobrecarga. E vetores são muito bons nisso.
adicionado o autor paercebal, fonte
@nobar: Depende do construtor MyStruct. Se estiver vazio, e inlined, e os membros do MyStruct tiverem um construtor de custo zero, então o compilador C ++ irá otimizá-lo para nada. Então, não vamos pagar por isso. Apenas para o redimensionamento.
adicionado o autor paercebal, fonte
Parece que o vetor nos decepcionou nesse caso. Nós pagamos pela inicialização, mesmo se não precisarmos ou quisermos. Isto é garantido pela semântica de insert() que é chamada por resize (). O valor usado para inicialização é baseado no que quer que esteja no MyStruct passado para resize (). Desde que você não especificou nada quando você chamou resize (), o construtor padrão foi usado. Como o construtor padrão não faz nada nesse caso, você pode obter zeros ou obter outra coisa. De qualquer forma, você paga pela inicialização realizada por resize ().
adicionado o autor nobar, fonte
Se você não tiver nenhum construtor definido e todos os elementos forem do tipo POD, o construtor não fará nada. Se os elementos não forem PODs, eles simplesmente chamarão seus construtores padrão.
adicionado o autor Greg Rogers, fonte
Neste caso, MyStruct tem um construtor trivial, então nada é inicializado. Isso pode ser diferente da situação do OP.
adicionado o autor Greg Rogers, fonte

Os próprios structs precisam estar em uma memória contígua, ou você pode ter um vetor de struct *?

Os vetores fazem uma cópia do que você adicionar a eles, portanto, usar vetores de ponteiros em vez de objetos é uma maneira de melhorar o desempenho.

0
adicionado
Eles têm que ser contíguos. Eles estão em um buffer que está prestes a ser enviado pela rede como um grande pedaço.
adicionado o autor Jim Hunziker, fonte

Eu não acho que o STL é sua resposta. Você precisará rolar seu próprio tipo de solução usando realloc (). Você terá que armazenar um ponteiro e o tamanho ou o número de elementos e usá-lo para descobrir onde começar a adicionar elementos após um 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
adicionado

De seus comentários para outros cartazes, parece que você ficou com malloc() e amigos. O vetor não permite que você tenha elementos não construídos.

0
adicionado

A partir do seu código, parece que você tem um vetor de estruturas, cada uma com 2 inteiros. Você poderia usar 2 vetores de ints? Então

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

Agora você não paga por copiar uma estrutura a cada vez.

0
adicionado
Interessante. Isso pode funcionar - parece evitar a construção de um objeto intermediário.
adicionado o autor nobar, fonte

Eu faria algo como:

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

Você precisa definir um ctor para o tipo que está armazenado no memberVector, mas isso é um custo pequeno, já que lhe dará o melhor dos dois mundos; nenhuma inicialização desnecessária é feita e nenhuma realocação ocorrerá durante o loop.

0
adicionado
Isso não parece resolver o problema, pois ele usa um MyType() temporário e copia isso no vetor. Ainda há uma inicialização dupla.
adicionado o autor nobar, fonte

std::vector must initialize the values in the array somehow, which means some constructor (or copy-constructor) must be called. The behavior of vector (or any container class) is undefined if you were to access the uninitialized section of the array as if it were initialized.

A melhor maneira é usar reserve() e push_back() , para que o construtor de cópia seja usado, evitando a construção padrão.

Usando seu código de exemplo:

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

std::vector 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]));
    }
}

O único problema em chamar reserve() (ou resize() ) é que você pode acabar invocando o construtor de cópia com mais freqüência do que você precisa. Se você puder fazer uma boa previsão quanto ao tamanho final da matriz, é melhor reservar() o espaço uma vez no começo. Se você não sabe o tamanho final, pelo menos o número de cópias será mínimo em média.

In the current version of C++, the inner loop is a bit inefficient as a temporary value is constructed on the stack, copy-constructed to the vectors memory, and finally the temporary is destroyed. However the next version of C++ has a feature called R-Value references (T&&) which will help.

A interface fornecida por std :: vector não permite outra opção, que é usar alguma classe de fábrica para construir valores diferentes do padrão. Aqui está um exemplo aproximado de como esse padrão seria implementado em C ++:

template 
class my_vector_replacement {

   //...

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

Isso significa que você precisa criar sua própria classe de vetor. Neste caso, também complica o que deveria ter sido um exemplo simples. Mas pode haver momentos em que o uso de uma função de fábrica como essa é melhor, por exemplo, se a inserção estiver condicionada a algum outro valor, e você teria que construir incondicionalmente algum temporário caro, mesmo que não fosse realmente necessário.

0
adicionado

Se você realmente insistir em ter os elementos não inicializados e sacrificar alguns métodos como front (), back (), push_back (), use o vetor boost a partir do numérico. Ele permite que você não preserve elementos existentes ao chamar resize() ...

0
adicionado

Você pode usar um tipo de wrapper em torno do seu tipo de elemento, com um construtor padrão que não faz nada. Por exemplo.:

template 
struct no_init
{
    T value;

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

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

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

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

Usar:

std::vector> vec;
vec.resize(2ul * 1024ul * 1024ul * 1024ul);
0
adicionado