Хорошо ли использовать 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 был СДЕЛАНО для использования в таких случаях. Итак, да.
Ответ 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.