¿Es una buena práctica usar std:: vector como un búfer simple?


Tengo una aplicación que está realizando algún procesamiento en algunas imágenes.

Dado que conozco el ancho / alto / formato, etc. (Lo hago), y pensando solo en definir un búfer para almacenar los datos de píxeles:

Entonces, en lugar de usar new y delete [] en un unsigned char* y mantener una nota separada del tamaño del búfer, estoy pensando en simplificar las cosas usando un std::vector.

Así que declararía mi clase algo como esto:

#include <vector>

class MyClass
{
    // ... etc. ...

public:
    virtual void OnImageReceived(unsigned char *pPixels, 
        unsigned int uPixelCount);

private:
    std::vector<unsigned char> m_pImageBuffer;    // buffer for 8-bit pixels

    // ... etc. ...
};

Entonces, cuando recibí un nuevo imagen (de algún tamaño variable-pero no te preocupes por esos detalles aquí), solo puedo cambiar el tamaño del vector (si es necesario) y copiar los píxeles:

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
    // called when a new image is available
    if (m_pImageBuffer.size() != uPixelCount)
    {
        // resize image buffer
        m_pImageBuffer.reserve(uPixelCount);
        m_pImageBuffer.resize(uPixelCount, 0);
    }

    // copy frame to local buffer
    memcpy_s(&m_pImageBuffer[0], m_pImageBuffer.size(), pPixels, uPixelCount);

    // ... process image etc. ...
}

Esto me parece bien, y me gusta el hecho de que no tengo que preocuparme por la gestión de la memoria, pero plantea algunas preguntas:

  1. ¿Es esta una aplicación válida de std::vector o hay un contenedor más adecuado?
  2. ¿Estoy haciendo lo correcto en cuanto a rendimiento al llamar reserve y resize?
  3. Will it always be the case that the underlying memory is consecutive so I can use memcpy_s as shown?

Cualquier comentario adicional, crítica o consejo sería muy bienvenido.

Author: Roger Rowland, 2013-10-23

8 answers

  1. Claro, esto funcionará bien. Lo único de lo que debe preocuparse es asegurarse de que el búfer esté correctamente alineado, si su clase se basa en una alineación particular; en este caso, es posible que desee usar un vector del tipo de datos en sí (como float).
  2. No, la reserva no es necesaria aquí; redimensionar aumentará automáticamente la capacidad según sea necesario, exactamente de la misma manera.
  3. Antes de C++03, técnicamente no (pero en la práctica sí). Desde C++03, sí.

Incidentalmente, sin embargo, memcpy_s no es el enfoque idiomático aquí. Use std::copy en su lugar. Tenga en cuenta que un puntero es un iterador.

A partir de C++17, std::byte es la unidad idiomática de almacenamiento con escritura opaca como la que está utilizando aquí. char seguirá funcionando, por supuesto, pero permite usos inseguros (como char!) que byte no lo hace.

 36
Author: Sneftel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-22 17:28:00

Además de lo que otras respuestas mencionan, te recomendaría usar std::vector::assign en lugar de std::vector::resize y memcpy:

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
    m_pImageBuffer.assign(pPixels, pPixels + uPixelCount);
}

Que redimensionará si es necesario, y estaría evitando la innecesaria 0 inicialización del búfer causada por std::vector::resize.

 18
Author: mfontanini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-23 13:23:19

Usar un vector en este caso está bien. En C++ se garantiza que el almacenamiento es contiguo.

No me gustaría tanto resize y reserve, ni yo memcpy para copiar los datos. En su lugar, todo lo que necesita hacer es reserve para asegurarse de que no tiene que reasignar muchas veces, luego borrar el vector usando clear. Si resize, va a ir a través y establecer los valores de cada elemento a sus valores predeterminados this esto es unnecesarry aquí porque solo vas a sobrescribirlo de todos modos.

Cuando está listo para copiar los datos, no use memcpy. Use copy junto con back_inserter en un vector vacío:

std::copy (pPixels, pPixels + uPixelCount, std::back_inserter(m_pImageBuffer));

Consideraría que este modismo es mucho más canónico que el método memcpy que estás empleando. Puede haber métodos más rápidos o más eficientes, pero a menos que pueda probar que esto es un cuello de botella en su código (que probablemente no lo será; tendrá peces mucho más grandes para freír en otros lugares) Me quedaría con métodos idiomáticos y dejaría el prematuro micro-optimizaciones a otra persona.

 14
Author: John Dibling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-31 17:02:53

Std::vector se hizo para ser utilizado en tales casos. Así que, sí.

  1. Sí, lo es.

  2. reserve es innecesario en su caso.

  3. Sí, lo hará.

 3
Author: Ivan Ishchenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-23 13:13:14

Además-para asegurar un mínimo de memoria asignada:

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
    m_pImageBuffer.swap(std::vector<unsigned char>(
         pPixels, pPixels + uPixelCount));
    // ... process image etc. ...
}

Vector:: assign no cambia la cantidad de memoria asignada, si la capacidad es mayor que la cantidad necesaria:

Efectos: erase (begin (), end()); insert (begin (), first, last);

 2
Author: Dieter Lücking,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-23 15:05:20

Por favor, considere esto:

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
    // called when a new image is available
    if (m_pImageBuffer.size() != uPixelCount) // maybe just <  ??
    {
        std::vector<unsigned char> temp;
        temp.reserve(uPixelCount);        // no initialize
        m_pImageBuffer.swap(temp) ;       // no copy old data
    }

    m_pImageBuffer.assign(pPixels, pPixels + uPixelCount);  // no reallocate

    // ... process image etc. ...
}

Mi punto es que si tiene una imagen grande y necesita una foto más grande de litter, su foto antigua obtendrá una copia durante la reserva y/o redimensionará en el nuevo memmory asignado, el exceso de memmory inicializado, y luego reescrito con la nueva foto. Usted colud assing directamente, pero entonces usted no será capaz de utilizar la información que tiene sobre el nuevo tamaño para evitar posibles reasignaciones (tal vez la implementación de asignar está allready optimizar para este caso simple ????).

 2
Author: qPCR4vir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-30 11:29:46

Depende. Si accede a los datos solo a través de iteradores y el operador [], entonces está bien usar un vector.

Si tiene que dar un puntero a las funciones que esperan un búfer de, por ejemplo, bytes. No lo es en mi opinión. En este caso, debe usar algo como

unique_ptr<unsigned char[]> buf(new unsigned char[size])

Es tan guardar como un vector, pero en lugar de un vector tiene el máximo control del búfer. Un vector puede reasignar un búfer o durante una llamada a un método / función puede hacer involuntariamente una copia de su todo el vector. Un error fácil de cometer.

La regla (para mí) es. Si tienes un vector, úsalo como un vector. Si necesita un búfer de memoria, utilice un búfer de memoria.

Como en un comentario señalado, el vector tiene un método de datos. Esto es C++. La libertad de usar un vector como un buffer raw no repara que debas usarlo como un buffer raw. En mi humilde opinión, la intención de un vector era tener un buffer de guardado de tipo con el sistema de acceso de guardado de tipo. Para la compatibilidad se puede utilizar el buffer interno para llamadas. La intención no era utilizar el vector como un contenedor de búfer de puntero inteligente. Para eso, utilizo las plantillas de puntero, señalando a otro usuario de mi código que uso este búfer de una manera raw. Si utilizo vectores, los utilizo en la forma en que están destinados a, no las formas posibles que ofrecen.

YA que tengo alguna culpa aquí por mi opinión (no recomendación) quiero agregar algunas palabras al problema real que el op describió.

Si espera siempre la misma imagen tamaño, él debe, en mi opinión, utilizar un unique_ptr, porque eso es lo que está haciendo con él en mi opinión. Usando

 m_pImageBuffer.resize(uPixelCount, 0);

Ceros el búfer primero antes de que copie el pPixel, una penalización de tiempo innecesaria.

Si las imágenes que está esperando de diferente tamaño, debería, en mi opinión, no usar un vector durante la siguiente razón. Especialmente en su código:

// called when a new image is available
if (m_pImageBuffer.size() != uPixelCount)
{
    // resize image buffer
    m_pImageBuffer.reserve(uPixelCount);
    m_pImageBuffer.resize(uPixelCount, 0);
}

Redimensionará el vector, que de hecho es un malloc y copiará mientras las imágenes se hagan más grandes. Un realloc en mi experiencia siempre conduce a malloc y copia.

Esa es la razón por la que, especialmente en esta situación, recomiendo el uso de un unique_ptr en lugar de un vector.

 0
Author: Martin Schlott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-23 17:05:45

Evitaría std:: vector como un contenedor para almacenar un búfer no estructurado, ya que std::vector es profundamente lento cuando se usa como búfer

Considere este ejemplo:

#include <chrono>
#include <ctime>
#include <iostream>
#include <memory>
#include <vector>

namespace {
std::unique_ptr<unsigned char[]> allocateWithPtr() {
    return std::unique_ptr<unsigned char[]>(new unsigned char[4000000]);
}

std::vector<unsigned char> allocateWithVector() {
    return std::vector<unsigned char>(4000000); }
}

int main() {
    auto start = std::chrono::system_clock::now();

    for (long i = 0; i < 1000; i++) {
        auto myBuff = allocateWithPtr();
    }
    auto ptr_end = std::chrono::system_clock::now();

    for (long i = 0; i < 1000; i++) {
        auto myBuff = allocateWithVector();
    }
    auto vector_end = std::chrono::system_clock::now();

    std::cout << "std::unique_ptr = " 
              << (ptr_end - start).count() / 1000.0 << " ms." << std::endl;
    std::cout << "std::vector = " 
              << (vector_end - ptr_end).count() / 1000.0 << " ms." << std::endl;
}

Salida:

bash-3.2$ time myTest
std::unique_ptr = 0.396 ms.
std::vector = 35341.1 ms.

real    0m35.361s
user    0m34.932s
sys 0m0.092s

Incluso sin escrituras o reasignaciones, std::vector es casi 100.000 veces más lento que usar un nuevo con un unique_ptr. ¿Qué está pasando aquí?

Como señala @MartinSchlott, no está diseñado para esta tarea. Un vector es para contener instancias de un objeto de conjunto, no un búfer no estructurado (desde un punto de vista de matriz). Los objetos tienen destructores y constructores. Cuando el vector es destruido, llama al destructor para cada elemento en él, incluso vector llamará a un destructor para cada carácter en su vector.

Puedes ver cuánto tiempo lleva solo "destruir" los caracteres sin signo en este vector con este ejemplo:

#include <chrono>
#include <ctime>
#include <iostream>
#include <memory>
#include <vector>

std::vector<unsigned char> allocateWithVector() {
    return std::vector<unsigned char>(4000000); }
}

int main() {
    auto start = std::chrono::system_clock::now();

    for (long i = 0; i < 100; i++) {
        auto leakThis = new std::vector<unsigned char>(allocateWithVector());
    }
    auto leak_end = std::chrono::system_clock::now();

    for (long i = 0; i < 100; i++) {
        auto myBuff = allocateWithVector();
    }
    auto vector_end = std::chrono::system_clock::now();

    std::cout << "leaking vectors: = " 
              << (leak_end - start).count() / 1000.0 << " ms." << std::endl;
    std::cout << "destroying vectors = " 
              << (vector_end - leak_end).count() / 1000.0 << " ms." << std::endl;
}

Salida:

leaking vectors: = 2058.2 ms.
destroying vectors = 3473.72 ms.

real    0m5.579s
user    0m5.427s
sys 0m0.135s

Incluso cuando se elimina la destrucción del vector, todavía está tomando 2 segundos para construir solo 100 de estas cosas.

Si no necesita cambiar el tamaño dinámico, o la construcción y destrucción de los elementos que componen su búfer, no use std::vector.

 0
Author: Steve Broberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-08 17:33:58