为什么使用MSVC 2015编译器,std :: vector <uint8_t> ::: insert的工作速度比std :: copy快5倍?

时间:2018-08-20 11:43:36

标签: c++

我有一个微不足道的功能,可以将一个字节块复制到std :: vector:

std::vector<uint8_t> v;

void Write(const uint8_t * buffer, size_t count)
{
    //std::copy(buffer, buffer + count, std::back_inserter(v));

    v.insert(v.end(), buffer, buffer + count);
}

v.reserve(<buffer size>);
v.resize(0);

Write(<some buffer>, <buffer size>);

如果我使用std::vector<uint8_t>::insert,它的工作速度比我使用std::copy快5倍。

我尝试使用启用和禁用优化的MSVC 2015编译此代码,并得到相同的结果。

std::copystd::back_inserter实现中看起来有些奇怪。

3 个答案:

答案 0 :(得分:5)

在编写标准库实现时要牢记性能,但是只有在优化打开的情况下才能实现性能。

//This reduces the performance dramatically if the optimization is switched off.

尝试在优化关闭的情况下评估功能性能与asking ourselves if the law of gravitation would still be true if there were no mass left in the Universe一样毫无意义。

答案 1 :(得分:3)

v.insert的调用正在调用容器的成员函数。成员函数知道容器的实现方式,因此它可以执行更通用的算法无法执行的操作。特别是,在将随机访问迭代器指定的值范围插入向量中时,该实现知道要添加多少个元素,因此它可以一次调整内部存储的大小,然后只需复制这些元素即可。

另一方面,使用插入迭代器对std::copy的调用必须为每个元素调用insert。它无法预分配,因为std::copy使用序列,而不是容器;它不知道如何调整容器的大小。因此,对于向向量的大插入,每次向量满时需要重新调整内部存储器的大小,并且需要重新插入。重新分配的开销是摊销固定时间,但是该常数比仅进行一次调整大小时的常数大。

通过调用reserve(我忽略了,谢谢@ChrisDrew),重新分配的开销并不那么大。但是insert的实现知道要复制多少个值,并且知道这些值在内存中是连续的(因为迭代器是指针),并且知道这些值是可微复制的,因此它将使用std::memcpy一次将所有位爆破。对于std::copy,这都不适用;后面的插入程序必须检查是否需要重新分配,并且无法优化代码,因此您最终会得到一个循环,该循环一次复制一个元素,并检查为每个元素分配的空间的结尾。这比普通的std::memcpy要贵得多。

通常,算法对所访问的数据结构的内部了解越多,则速度越快。 STL算法是通用的,与专用于容器的算法相比,这种通用的开销可能更大。

答案 2 :(得分:1)

通过std::vector的良好实现,v.insert(v.end(), buffer, buffer + count);可以实现为:

size_t count = last-first;
resize(size() + count);
memcpy(data+offset, first, count);
另一方面,

std::copy(buffer, buffer + count, std::back_inserter(v))将实现为:

while ( first != last )
{
   *output++ = *first++;
}

等效于:

while ( first != last )
{
   v.push_back( *first++ );
}

或(大致):

while ( first != last )
{
   // push_back should be slightly more efficient than this
   v.resize(v.size() + 1);
   v.back() = *first++;
}

虽然理论上编译器可以将上述内容优化为memcpy,但最好的情况是,您最多可能会内联方法,这样就不会产生函数调用开销,但仍然一次写入一个字节,而memcpy通常将使用向量指令一次复制多个字节。