Хорошо ли использовать std::vector как простой буфер?

У меня есть приложение, которое выполняет некоторую обработку на некоторых изображениях.

Учитывая, что я знаю ширину/высоту/формат и т.д. (я знаю) и думаю только об определении буфера для хранения данных пикселей:

Затем вместо использования new и delete [] на unsigned char* и сохраняя отдельное примечание о размере буфера, я думаю об упрощении вещей, используя std::vector.

Итак, я бы объявил свой класс примерно так:

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

Затем, когда я получил новое изображение (определенного размера переменной, но не беспокоиться об этих деталях здесь), я могу просто изменить размер вектора (при необходимости) и скопировать пиксели:

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

Мне это кажется прекрасным, и мне нравится тот факт, что мне не нужно беспокоиться об управлении памятью, но возникает ряд вопросов:

  • Это допустимое приложение std::vector или есть более подходящий контейнер?
  • Я делаю правильную работу по производительности, вызывая reserve и resize?
  • Всегда ли будет так, что базовая память последовательна, поэтому я могу использовать memcpy_s, как показано?

Любые дополнительные комментарии, критика или советы были бы очень желанными.

Ответ 1

  • Конечно, все будет хорошо. Единственное, что вам нужно беспокоиться, это обеспечить правильное выравнивание буфера, если ваш класс полагается на конкретное выравнивание; в этом случае вы можете захотеть использовать вектор самого типа данных (например, float).
  • Нет, резерв здесь не нужен; изменение размера автоматически увеличит емкость по мере необходимости, точно так же.
  • До С++ 03 технически нет (но на практике да). Начиная с С++ 03, да.

Кстати, хотя memcpy_s здесь не является идиоматическим подходом. Вместо этого используйте std::copy. Имейте в виду, что указатель является итератором.

Ответ 2

Кроме того, что касается других ответов, я бы рекомендовал использовать std::vector::assign, а не std::vector::resize и memcpy:

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

При необходимости изменится размер, и вы избежите ненужной инициализации буфера 0, вызванного std::vector::resize.

Ответ 3

Использование a vector в этом случае прекрасное. На С++ хранилище гарантированно будет иметь доступ к контенту.

Я бы не стал resize и reserve, и я бы не мог memcpy копировать данные. Вместо этого вам нужно всего лишь reserve, чтобы вы не перераспределяли много раз, затем очистите vector с помощью clear. Если вы resize, он пройдет и установит значения каждого элемента в значения по умолчанию - здесь это noecesarry, потому что вы все равно переписываете его.

Когда вы будете готовы скопировать данные, не используйте memcpy. Используйте copy в сочетании с back_inserter в пустой vector:

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

Я бы счел эту идиому намного ближе к каноническому, чем метод memcpy, который вы используете. Могут быть более быстрые или более эффективные методы, но если вы не сможете доказать, что это узкое место в вашем коде (чего, вероятно, не будет, у вас будет гораздо больше рыбы, чтобы жарить в другом месте) Я бы придерживался идиоматических методов и ушел преждевременные микро-оптимизации для кого-то другого.

Ответ 4

std::vector был СДЕЛАНО для использования в таких случаях. Итак, да.

  • Да, это так.

  • reserve не требуется в вашем случае.

  • Да, это будет.

Ответ 5

Кроме того - для обеспечения минимальной выделенной памяти:

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

vector:: assign не изменяет объем выделенной памяти, если емкость больше требуемой суммы:

Эффекты:     erase (begin(), end());     insert (begin(), first, last);

Ответ 6

Пожалуйста, подумайте над этим:

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

Моя точка зрения заключается в том, что если у вас есть большая картина и вам нужно больше помета, ваш старый pic получит копию во время резерва и/или изменит размер в новую выделенную memmory, избыток memmory, инициализированный, а затем переписан с помощью новый рис. Вы разобрались напрямую, но тогда вы не сможете использовать информацию о новом размере, чтобы избежать возможных перераспределений (возможно, реализация назначения уже оптимизирована для этого простого случая????).

Ответ 7

Это зависит. Если вы получаете доступ к данным только через итераторы и оператор [], чем это удобно для использования вектора.

Если вам нужно указать указатель на функции, которые ожидают буфер, например. байт. Это не на мой взгляд. В этом случае вы должны использовать что-то вроде

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

это как сохранение как вектор, но вместо вектора вы имеете максимальный контроль над буфером. Вектор может перераспределять буфер или во время вызова метода/функции, вы можете непреднамеренно сделать копию всего вашего вектора. Легко сделанная ошибка.

Правило (для меня). Если у вас есть вектор, используйте его как вектор. Если вам нужен буфер памяти, используйте буфер памяти.

Как отмечалось в комментарии, вектор имеет метод данных. Это С++. Свобода использования вектора как необработанного буфера не исправляет, что вы должны использовать его в качестве необработанного буфера. По моему скромному мнению, намерение вектора состояло в том, чтобы иметь буфер сохранения типа с системой доступа типа save. Для совместимости вы можете использовать внутренний буфер для вызовов. Цель заключалась в том, чтобы не использовать вектор в качестве контейнера буфера интеллектуального указателя. Для этого я использую шаблоны указателей, сигнализируя другим пользователям моего кода, что я использую этот буфер необработанным способом. Если я использую векторы, я использую их так, как они предназначены, а не возможные способы их использования.

AS Я получил некоторую вину за мое мнение (не рекомендация). Я хочу добавить несколько слов к реальной проблеме, описанной в op.

Если он ожидает, что всегда будет одинаковый размер изображения, он должен, на мой взгляд, использовать unique_ptr, потому что это то, что он делает с этим, по моему мнению. Используя

 m_pImageBuffer.resize(uPixelCount, 0);

нули буфер сначала, прежде чем он скопирует pPixel к нему, ненужное ограничение времени.

Если фотографии, которые он ожидает разного размера, он должен, на мой взгляд, не использовать вектор по следующей причине. Особенно в его коде:

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

он изменит размер вектора, который на самом деле является malloc и копией, пока изображения становятся все больше. Realloc в моем опыте всегда приводит к malloc и копированию.

Вот почему я, особенно в этой ситуации, рекомендую использовать unique_ptr вместо вектора.

Ответ 8

Я бы избегал std::vector в качестве контейнера для хранения неструктурированного буфера, поскольку std::vector является глубоко медленным при использовании в качестве буфера

Рассмотрим следующий пример:

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

Вывод:

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

Даже без записи или перераспределения, std::vector почти в 100 000 раз медленнее, чем просто использование нового с уникальным_ptr. Что здесь происходит?

Как отмечает @MartinSchlott, он не предназначен для этой задачи. Вектор предназначен для хранения экземпляров набора объектов, а не неструктурированного (с точки зрения массива) буфера. Объекты имеют деструкторы и конструкторы. Когда вектор уничтожается, он вызывает деструктор для каждого элемента в нем, даже вектор будет вызывать деструктор для каждого char в вашем векторе.

Вы можете видеть, сколько времени требуется для "уничтожения" неподписанных символов в этом векторе с помощью этого примера:

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

Вывод:

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

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

Даже при удалении уничтожения вектора все еще требуется 2 секунды, чтобы просто построить 100 из этих вещей.

Если вам не требуется динамическое изменение размера или построение и уничтожение элементов, составляющих ваш буфер, не используйте std::vector.