Vectores STL con almacenamiento no inicializado?

Estoy escribiendo un bucle interno que necesita colocar struct en un almacenamiento contiguo. No sé cuántos de estos struct habrá antes de tiempo. Mi problema es que el vector de STL inicializa sus valores a 0, así que no importa lo que haga, incurro en el costo de la inicialización más el costo de establecer los miembros de struct a sus valores.

¿Hay alguna manera de evitar la inicialización, o hay un contenedor similar a STL con almacenamiento contiguo redimensionable y elementos no inicializados?

(Estoy seguro de que esta parte del código debe optimizarse, y estoy seguro de que la inicialización es un costo significativo).

Además, vea mis comentarios a continuación para obtener una aclaración sobre cuándo ocurre la inicialización.

ALGO DE 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
agregado editado
Puntos de vista: 1
Otra aclaración: no es que el constructor inicialice los valores en 0. Es que el tamaño de las llamadas se inserta, lo que hace.
agregado el autor Jim Hunziker, fuente
Nota: usar reserve() no es una solución, ya que no se puede acceder legalmente a los datos que se encuentran en las ubicaciones end() y superiores.
agregado el autor Jim Hunziker, fuente
NOTA: de todos modos, no se puede acceder a los datos sin inicializar. El mismo problema para el vector pasado .end() y los miembros no inicializados de un T []. Pero con un vector, lo más probable es que el código de depuración lo indique ahora. El código de matriz fallará silenciosamente en la PC del cliente.
agregado el autor MSalters, fuente
También vea stackoverflow.com/q/7218574/1969455 para obtener un enfoque de asignación. (bueno para los tipos de POD)
agregado el autor Matthäus Brandl, fuente
¿Podría darnos la declaración de estructura también? Gracias... :-)
agregado el autor paercebal, fuente
Esta es una buena pregunta. Para algunas aplicaciones, es importante tener en cuenta que std :: vector siempre inicializa sus elementos, incluso si son datos antiguos (POD).
agregado el autor nobar, fuente

14 Respuestas

Así que aquí está el problema, resize is calling insert, que está haciendo una construcción de copia desde un elemento construido por defecto para cada uno de los elementos recién agregados. Para hacer esto con un costo de 0, necesita escribir su propio constructor predeterminado Y su propio constructor de copia como funciones vacías. Hacer esto al constructor de copia es una muy mala idea porque romperá los algoritmos de reasignación interna de std ::.

Resumen: No podrá hacer esto con std :: vector.

0
agregado
Este es el verdadero asunto. std :: vector debería darse cuenta de que no tiene que hacer ninguna inicialización si T tiene un constructor predeterminado trivial. Gracias por señalar que el constructor de copia es lo que está haciendo el trabajo innecesario aquí.
agregado el autor Eric Hein, fuente

Para aclarar sobre las respuestas de reserva (): necesita usar reserve() junto con push_back (). De esta forma, el constructor predeterminado no se llama para cada elemento, sino el constructor de copia. Aún incurre en la penalidad de configurar su estructura en la pila y luego copiarla en el vector. Por otro lado, es posible que si usa

vect.push_back(MyStruct(fieldValue1, fieldValue2))

el compilador construirá la nueva instancia directamente en la memoria que pertenece al vector. Depende de cuán inteligente sea el optimizador. Debe verificar el código generado para averiguarlo.

0
agregado
Resulta que el optimizador para gcc, en el nivel O3, no es lo suficientemente inteligente como para evitar la copia.
agregado el autor Jim Hunziker, fuente

C ++ 0x agrega una nueva plantilla de función de miembro emplace_back a vector (que se basa en plantillas variadic y reenvío perfecto) que elimina por completo cualquier temporal:

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

En C ++ 11 (e impulsar) puede utilizar la versión de matriz de unique_ptr para asignar una matriz no inicializada. Este no es un contenedor stl completo, pero aún se maneja con memoria y C ++ - ish, que será lo suficientemente bueno para muchas aplicaciones.

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

Use el método std :: vector :: reserve (). No cambiará el tamaño del vector, pero asignará el espacio.

0
agregado

Errar...

prueba el método:

std::vector::reserve(x)

Le permitirá reservar suficiente memoria para x elementos sin inicializar ninguno (su vector aún está vacío). Por lo tanto, no habrá reasignación hasta que se pase por x.

El segundo punto es que el vector no inicializará los valores a cero. ¿Estás probando tu código en depuración?

Después de la verificación en g ++, el siguiente 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 ;
}

da los siguientes resultados:

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

La inicialización que viste fue probablemente un artefacto.

[EDITAR] Después del comentario sobre el cambio de tamaño, modifiqué el código para agregar la línea de cambio de tamaño. El cambio de tamaño llama efectivamente al constructor predeterminado del objeto dentro del vector, pero si el constructor predeterminado no hace nada, entonces nada se inicializa ... Todavía creo que fue un artefacto (me las arreglé la primera vez para tener el vector completo con el cero siguiente código:

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

Asi que... : - /

[EDIT 2] Como ya ha ofrecido Arkadiy, la solución es usar un constructor en línea que tome los parámetros deseados. Algo como

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

Esto probablemente será incluido en tu código.

Pero, de todos modos, debe estudiar su código con un generador de perfiles para asegurarse de que este fragmento de código sea el cuello de botella de su aplicación.

0
agregado
Escribí una nota arriba. No es el constructor de vector que se inicializa a 0. Es resize() que lo hace.
agregado el autor Jim Hunziker, fuente
Creo que estás en el camino correcto. No tengo ningún constructor definido en la estructura, por lo que su constructor predeterminado (creo) se inicializa a cero. Verifico si agregar un constructor predeterminado que no resuelve el problema.
agregado el autor Jim Hunziker, fuente
Greg Rogers tiene razón. Mi suposición es que la memoria era "cero" debido a alguna inicialización del proceso independiente del código que escribí. En C ++, no paga por algo que no usa. Entonces, si está escribiendo un código tipo C, no debería tener sobrecarga. Y los vectores son bastante buenos en eso.
agregado el autor paercebal, fuente
@nobar: depende del constructor MyStruct. Si está vacío y en línea, y los miembros de MyStruct tienen un cero constructores de costo, entonces el compilador de C ++ lo optimizará a nada. Entonces, no pagaremos por eso. Solo para cambiar el tamaño.
agregado el autor paercebal, fuente
Parece que el vector nos ha decepcionado en este caso. Pagamos la inicialización, incluso si no la necesitamos o no queremos. Esto está garantizado por la semántica de insert() que se llama por resize (). El valor utilizado para la inicialización se basa en lo que sea que esté en el MyStruct pasado a resize (). Como no especificó nada cuando llamó a resize (), se utilizó el constructor predeterminado. Como el constructor predeterminado no hace nada en este caso, puede obtener ceros o puede obtener algo más. De cualquier manera, paga por la inicialización realizada por resize ().
agregado el autor nobar, fuente
Si no tiene definido un constructor y todos los elementos son tipos de POD, entonces el constructor no hace nada. Si los elementos no son POD, simplemente llamaría a sus constructores por defecto.
agregado el autor Greg Rogers, fuente
En este caso, MyStruct tiene un constructor trivial por lo que nada se inicializa. Esto puede ser diferente a la situación del OP.
agregado el autor Greg Rogers, fuente

¿Las estructuras mismas necesitan estar en la memoria contigua, o pueden salirse con la suya teniendo un vector de struct *?

Los vectores hacen una copia de lo que sea que les agregue, así que usar vectores de punteros en lugar de objetos es una forma de mejorar el rendimiento.

0
agregado
Deben ser contiguos. Están en un buffer que está a punto de ser enviado a través de la red como una gran parte.
agregado el autor Jim Hunziker, fuente

No creo que STL sea tu respuesta. Necesitará implementar su propio tipo de solución con realloc (). Tendrá que guardar un puntero y el tamaño o la cantidad de elementos, y usar eso para encontrar dónde comenzar a agregar elementos después de un 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
agregado

De tus comentarios a otros carteles, parece que te quedan malloc() y tus amigos. Vector no le permitirá tener elementos no construidos.

0
agregado

Desde su código, parece que tiene un vector de estructuras, cada una de las cuales consta de 2 ints. ¿Podrías usar 2 vectores de ints? Entonces

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

Ahora no paga por copiar una estructura cada vez.

0
agregado
Interesante. Esto podría funcionar, parece que evitaría la construcción de un objeto intermedio.
agregado el autor nobar, fuente

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

Necesita definir un ctor para el tipo que está almacenado en el memberVector, pero es un costo pequeño, ya que le dará lo mejor de ambos mundos; no se realiza una inicialización innecesaria y no se realizará ninguna reasignación durante el ciclo.

0
agregado
Esto no parece resolver el problema ya que usa un MyType temporal() y lo copia en el vector. Todavía hay una inicialización doble.
agregado el autor nobar, fuente

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.

La mejor manera es usar reserve() y push_back() , de modo que se use el constructor de copias, evitando la construcción por defecto.

Usando su código de ejemplo:

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

El único problema al llamar a reserve() (o resize() ) de esta manera es que puede terminar invocando el constructor de copia con más frecuencia de la necesaria. Si puede hacer una buena predicción sobre el tamaño final de la matriz, es mejor reservar() el espacio una vez al principio. Sin embargo, si no conoce el tamaño final, al menos la cantidad de copias será mínima en promedio.

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.

La interfaz suministrada por std :: vector no permite otra opción, que es usar alguna clase similar a la fábrica para construir valores distintos al predeterminado. Aquí hay un ejemplo aproximado de cómo se vería este patrón implementado en 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));
    }
}

Hacer esto significa que tienes que crear tu propia clase de vectores. En este caso, también complica lo que debería haber sido un simple ejemplo. Pero puede haber ocasiones en las que usar una función de fábrica como esta sea mejor, por ejemplo, si el inserto es condicional en algún otro valor, y de otro modo tendrías que construir incondicionalmente algún costoso temporal incluso si no fuera realmente necesario.

0
agregado

Si realmente insistes en tener los elementos sin inicializar y sacrificar algunos métodos como front (), back (), push_back (), usa boost vector desde numérico. Le permite incluso no conservar los elementos existentes al llamar a resize() ...

0
agregado

Puede usar un tipo de envoltorio alrededor de su tipo de elemento, con un constructor predeterminado que no hace nada. P.ej.:

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
agregado