[英]Why std::vector<uint8_t>::insert works 5 times faster than std::copy with MSVC 2015 compiler?
我有一个微不足道的功能,它将一个字节块复制到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::copy
或std::back_inserter
实现有些奇怪。
标准库实现的编写考虑了性能,但是只有在优化打开时才能实现性能。
//This reduces the performance dramatically if the optimization is switched off.
试图在优化关闭的情况下测量功能性能就像问自己,如果宇宙中没有质量,引力定律是否仍然成立,那毫无意义。
对v.insert
的调用正在调用容器的成员函数。 成员函数知道容器的实现方式,因此它可以执行更通用的算法无法执行的操作。 特别是,在将随机访问迭代器指定的值范围插入向量中时,该实现知道要添加多少个元素,因此它可以一次调整内部存储的大小,然后只需复制这些元素即可。
另一方面,使用insert-iterator调用std::copy
时,必须为每个元素调用insert
。 它不能预分配,因为std::copy
使用序列而不是容器; 它不知道如何调整容器的大小。 因此,对于向向量的大插入,每次向量满时需要重新调整内部存储器的大小,并且需要重新插入。 该重新分配的开销是摊销的固定时间,但是该常数比仅进行一次调整大小时的常数大得多。
通过调用reserve
(我忽略了,@ ChrisDrew,我忽略了它),重新分配的开销并不那么重要。 但是insert
的实现知道要复制多少个值,并且知道这些值在内存中是连续的(因为迭代器是指针),并且知道这些值是可微复制的,因此将使用std::memcpy
一次炸掉所有的碎片。 对于std::copy
,都不适用; 后面的插入程序必须检查是否需要重新分配,并且无法优化代码,因此您最终会得到一个循环,该循环一次复制一个元素,并检查为每个元素分配的空间的结尾。 这比普通的std::memcpy
要贵得多。
通常,算法对所访问的数据结构的内部了解越多,则速度越快。 STL算法是通用的,与容器特定算法相比,这种通用的开销可能会更大。
通过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通常将使用向量指令一次复制多个字节。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.