Является ли хорошей практикой использование 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. ...
}
Мне это кажется прекрасным, и мне нравится тот факт, что мне не нужно беспокоиться об управлении памятью, но это вызывает некоторые вопросы:
- правильно ли я поступаю с точки зрения производительности, позвонив
reserveи ещеresize? - воля это всегда случай, когда основная память последовательна, поэтому я могу использовать
memcpy_s, как показано?
Является ли это допустимым применением
std::vector или есть более подходящий контейнер?Любые дополнительные замечания, критические замечания или советы будут весьма приветствоваться.
8 ответов:
- Конечно, это сработает отлично. Единственное, о чем вам нужно беспокоиться, - это убедиться, что буфер правильно выровнен, если ваш класс полагается на определенное выравнивание; в этом случае вы можете использовать вектор самого типа данных (например,
float).- нет, резерв здесь не нужен; resize автоматически увеличит емкость по мере необходимости, точно таким же образом.
- до C++03, технически нет (но на практике да). Начиная с C++03, Да.
Кстати, хотя,
memcpy_sздесь не идиоматический подход. Вместо этого используйтеstd::copy. Имейте в виду, что указатель является итератором.Начиная с C++17,
std::byteявляется идиоматической единицей непрозрачно типизированного хранилища, такого как вы используете здесь.charвсе еще будет работать, конечно, но допускает небезопасное использование (какchar!) которогоbyteнет.
Помимо того, что другие ответы упоминают, я бы рекомендовал вам использовать
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.
Использование
vectorв этом случае прекрасно. В C++ хранилище гарантированно будет смежным.Я бы не стал ни
resize, ниreserve, Ниmemcpyкопировать данные. Вместо этого все, что вам нужно сделать, этоreserve, чтобы убедиться, что вам не придется перераспределять много раз, а затем очиститьvectorс помощьюclear. Если выresize, он пройдет и установит значения каждого элемента по умолчанию - это излишне, потому что вы просто собираетесь перезаписать его в любом случае.Когда вы готовы скопировать данные, не используйте
memcpy. Используйтеcopyв сочетании сback_inserterв пустойvector:Я бы счел эту идиому гораздо более близкой к канонической, чем методstd::copy (pPixels, pPixels + uPixelCount, std::back_inserter(m_pImageBuffer));memcpy, который вы используете. Возможно, есть более быстрые или более эффективные методы, но если вы не можете доказать, что это узкое место в вашем коде (чего, скорее всего, не будет; у вас будет гораздо больше рыбы, чтобы жарить в другом месте) , я бы придерживался идиоматических методов и оставил преждевременные микрооптимизации для кого-то еще.
Std:: vector был создан для использования в таких случаях. Так что да.
Да, это так.
reserveв вашем случае это излишне.Да, так и будет.
Кроме того-для обеспечения минимального объема выделенной памяти:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount) { m_pImageBuffer.swap(std::vector<unsigned char>( pPixels, pPixels + uPixelCount)); // ... process image etc. ... }Vector:: assign не изменяет объем выделенной памяти, если емкость больше необходимого объема:
Эффекты: стереть (begin (), end()); вставить (begin (), first, last);
Пожалуйста, подумайте вот о чем:
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. ... }Моя точка зрения заключается в том, что если у вас есть большая картинка и вам нужен более крупный снимок, ваш старый снимок будет скопирован во время резервирования и/или изменения размера в новый выделенный memmory, избыток memmory инициализируется, а затем перезаписывается с новым рисунком. Вы колуд непосредственно ассинг, но тогда вы не сможете использовать информацию, которую вы имеете о новом размере, чтобы избежать возможных перераспределений (возможно, реализация assign уже оптимизирована для этого простого случая ????).
Это зависит. Если вы получаете доступ к данным только через итераторы и оператор [], то можно использовать вектор.
Если вы должны дать указатель на функции, которые ожидают буфер, например, байт. На мой взгляд, это не так. В этом случае вы должны использовать что-то вроде
unique_ptr<unsigned char[]> buf(new unsigned char[size])Это как сохранить как вектор, но вместо вектора у вас есть максимальный контроль над буфером. Вектор может перераспределить буфер или во время вызова метода / функции вы можете непреднамеренно сделать копию вашего весь вектор. Легко допустимая ошибка.
Правило (для меня) таково. Если у вас есть вектор, используйте его как вектор. Если вам нужен буфер памяти, используйте буфер памяти.
Как указано в комментарии, Вектор имеет метод данных. Это C++. Свобода использования вектора в качестве необработанного буфера не означает, что вы должны использовать его в качестве необработанного буфера. По моему скромному мнению, намерение вектора состояло в том, чтобы иметь буфер сохранения типа с системой доступа сохранения типа. Для совместимости вы можете использовать внутренний буфер для вызовов. Цель состояла не в том, чтобы использовать вектор в качестве буферного контейнера интеллектуального указателя. Для этого я использую шаблоны указателей, сигнализируя другому пользователю моего кода, что я использую этот буфер необработанным способом. Если я использую векторы, я использую их так, как они предназначены, а не так, как они предлагают.
Поскольку я получил некоторую вину здесь за мое мнение (а не рекомендацию), я хочу добавить несколько слов к фактической проблеме, описанной ОП.
Если он ожидает всегда одну и ту же картину размер, он должен, на мой взгляд, использовать 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); }Именно поэтому я, особенно в этой ситуации, рекомендую использовать unique_ptr вместо вектора.
Я бы не стал использовать 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 раз медленнее, чем просто использование нового с unique_ptr. Что здесь происходит?
Как указывает @MartinSchlott, он не предназначен для этой задачи. Вектор предназначен для хранения экземпляров набора объектов, а не неструктурированный (с точки зрения массива) буфер. Объекты имеют деструкторы и конструкторы. Когда вектор уничтожен, он вызывает деструктор для каждого элемента в нем, даже вектор вызовет деструктор для каждого символа в вашем векторе.Вы можете увидеть, сколько времени требуется, чтобы просто "уничтожить" неподписанные символы в этом векторе с помощью этого примера:
#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.
Comments