61

我有一个应用程序正在对某些图像执行一些处理。

鉴于我知道宽度/高度/格式等(我知道),并且只想定义一个缓冲区来存储像素数据:

然后,我没有在 an 上使用newand并单独记录缓冲区大小,而是考虑使用.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. ...
}

这对我来说似乎很好,我喜欢这样一个事实,即我不必担心内存管理,但它提出了一些问题:

  1. 这是一个有效的应用程序std::vector还是有更合适的容器?
  2. reserve 通过调用and , 我在性能方面做对了resize吗?
  3. 底层内存是否总是连续的,所以我可以memcpy_s如图所示使用?

非常欢迎任何额外的评论、批评或建议。

4

8 回答 8

42
  1. 当然,这会很好。如果您的类依赖于特定的对齐方式,您需要担心的一件事是确保缓冲区正确对齐;在这种情况下,您可能希望使用数据类型本身的向量(如float)。
  2. 不,这里不需要预留;resize 将根据需要自动增加容量,方式完全相同。
  3. 在 C++03 之前,技术上不是(但实际上是)。从 C++03 开始​​,是的。

不过,顺便说一句,这memcpy_s不是惯用的方法。改为使用std::copy。请记住,指针是迭代器。

从 C++17 开始,std::byte是不透明类型存储的惯用单元,例如您在此处使用的。char当然,它仍然可以工作,但允许不安全的用法(如char!)byte

于 2013-10-23T13:10:23.660 回答
25

除了其他答案提到的之外,我建议您使用std::vector::assign而不是std::vector::resizeand memcpy

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

如有必要,这将调整大小,并且您将避免0std::vector::resize.

于 2013-10-23T13:23:19.553 回答
15

在这种情况下使用 avector很好。在 C++ 中,存储保证是连续的。

我不会同时使用resizeand reserve,也不会memcpy复制数据。相反,您需要做的就是reserve确保不必多次重新分配,然后清除vectorusing clear。如果你resize,它将通过并将每个元素的值设置为它们的默认值——这在这里是不必要的,因为你无论如何都会覆盖它。

当你准备好复制数据时,不要使用memcpy. copy与 into一起使用into back_insertera empty vector

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

我认为这个成语比memcpy您使用的方法更接近规范。可能有更快或更有效的方法,但除非你能证明这是你的代码中的一个瓶颈(它可能不会;你在其他地方会有更大的鱼要炒)我会坚持惯用的方法并离开过早地对其他人进行微优化。

于 2013-10-23T13:13:54.673 回答
7

我会避免将 std::vector 作为存储非结构化缓冲区的容器,因为 std::vector 在用作缓冲区时非常慢

考虑这个 (C++14) 示例(对于 C++11,您可以使用 shared 而不是唯一的 ptrs,但是您会注意到在运行时从向量中没有得到的数组示例中的轻微性能损失 - O3 或 -O2):

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

namespace {
std::unique_ptr<std::array<unsigned char, 4000000>> allocateWithPtr() {
  return std::make_unique<std::array<unsigned char, 4000000>>();
}

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

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 % clang++ -O3 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 0 ms.
std::vector = 0 ms

bash % clang++ -O2 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 0 ms.
std::vector = 0 ms.

bash % clang++ -O1 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 89.945 ms.
std::vector = 14135.3 ms.

bash % clang++ -O0 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 80.945 ms.
std::vector = 67521.1 ms.

即使没有写入或重新分配,std::vector 也比在 -O0 处使用带有 unique_ptr 的 new 慢 800 倍以上,在 -O1 处慢 150 倍。这里发生了什么?

正如@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

即使移除了向量的破坏,构建 100 个这样的东西仍然需要 2 秒。

如果您不需要动态调整大小或构造和销毁构成缓冲区的元素,请不要使用 std::vector。

于 2016-06-08T17:33:58.290 回答
3

在这种情况下使用 std::vector。所以,是的。

  1. 是的。

  2. reserve在您的情况下是不必要的。

  3. 是的,它会的。

于 2013-10-23T13:13:14.647 回答
2

此外 - 确保分配的内存最少:

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

vector::assign 不会改变分配的内存量,如果容量大于所需的量:

效果:擦除(开始(),结束());插入(开始(),第一个,最后一个);

于 2013-10-23T14:55:00.407 回答
2

请考虑一下:

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

我的观点是,如果你有一张大图片并且需要一张更大的图片,你的旧图片将在保留期间得到副本和/或调整到新分配的内存中,初始化多余的内存,然后用新图片重写。你colud直接assing,但是你将无法使用你拥有的关于新大小的信息来避免可能的重新分配(也许assign的实现已经针对这个简单的情况进行了优化????)。

于 2013-10-30T11:00:20.657 回答
1

这取决于。如果您仅通过迭代器和 [] 运算符访问数据,则可以使用向量。

如果您必须提供一个指向需要例如字节缓冲区的函数的指针。在我看来不是这样。在这种情况下,您应该使用类似

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

它是否保存为向量,但不是向量,而是您可以最大程度地控制缓冲区。向量可能会重新分配缓冲区,或者在方法/函数调用期间,您可能会无意中复制整个向量。一个容易犯的错误。

规则(对我来说)是。如果您有向量,请像使用向量一样使用它。如果需要内存缓冲区,请使用内存缓冲区。

正如评论中指出的那样,向量具有数据方法。这是 C++。将向量用作原始缓冲区的自由并不意味着您应该将其用作原始缓冲区。在我看来,vector 的目的是拥有一个带有类型保存访问系统的类型保存缓冲区。为了兼容性,您可以使用内部缓冲区进行调用。目的不是将向量用作智能指针缓冲区容器。为此,我使用指针模板,向我的代码的其他用户表明我以原始方式使用此缓冲区。如果我使用向量,我会按照它们预期的方式使用它们,而不是它们提供的可能方式。

因为我的意见(不是推荐)在这里受到了一些指责,所以我想为操作描述的实际问题添加一些文字。

如果他期望总是相同的图片大小,我认为他应该使用 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 而不是向量的原因。

于 2013-10-23T13:09:20.000 回答