Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/azure/12.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么使用MSVC 2015编译器,std::vector:insert的工作速度比std::copy快5倍?_C++ - Fatal编程技术网

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

C++ 为什么使用MSVC 2015编译器,std::vector:insert的工作速度比std::copy快5倍?,c++,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 s

我有一个简单的函数,它将字节块复制到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::insert,它的工作速度比使用std::copy快5倍


我试着用MSVC 2015编译这段代码,启用和禁用了优化,得到了相同的结果


std::copy或std::back_插入器实现似乎有些奇怪。

标准库实现是在考虑性能的情况下编写的,但只有在启用优化时才能实现性能


尝试在关闭优化的情况下测量函数性能是毫无意义的。

标准库实现是在考虑性能的情况下编写的,但只有在打开优化的情况下才能实现性能

尝试在关闭优化的情况下测量函数性能是毫无意义的。

对v.insert的调用是调用容器的成员函数。成员函数知道容器是如何实现的,因此它可以做一些更通用的算法无法做的事情。特别是,当将随机访问迭代器指定的一系列值插入到向量中时,实现知道要添加多少元素,因此它可以一次性调整内部存储的大小,然后只复制元素

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

谢天谢地,@ChrisDrew,由于我忽略了对reserve的调用,重新分配的开销并没有那么大。但是insert的实现知道有多少个值被复制,它知道这些值在内存中是连续的,因为迭代器是指针,并且它知道这些值是可以复制的,所以它将使用std::memcpy一次将所有的位压缩。对于std::copy,这些都不适用;反向插入器必须检查是否需要重新分配,并且代码无法优化,因此最终会出现一个一次复制一个元素的循环,检查每个元素分配的空间是否结束。这比普通的std::memcpy要贵得多

一般来说,算法对其访问的数据结构的内部了解越多,速度就越快。STL算法是通用的,这种通用性的开销可能比特定于容器的算法的开销更大。

对v.insert的调用是调用容器的成员函数。成员函数知道容器是如何实现的,因此它可以做一些更通用的算法无法做的事情。特别是,当将随机访问迭代器指定的一系列值插入到向量中时,实现知道要添加多少元素,因此它可以一次性调整内部存储的大小,然后只复制元素

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

谢天谢地,@ChrisDrew,由于我忽略了对reserve的调用,重新分配的开销并没有那么大。但是insert的实现知道有多少个值被复制,它知道这些值在内存中是连续的,因为迭代器是指针,并且它知道这些值是可以复制的,所以它将使用std::memcpy一次将所有的位压缩。对于std::copy,这些都不适用;反向插入器必须检查是否需要重新分配,并且代码无法优化,因此最终会出现一个一次复制一个元素的循环,检查每个元素分配的空间是否结束。这比普通的std::memcpy要贵得多


一般来说,算法对其访问的数据结构的内部了解越多,速度就越快。STL算法是通用的,这种通用性的成本可能比特定于容器的算法的成本更高。

使用std::vector、v.insertv.end、buffer、buffer+count的良好实现;可实施为:

size_t count = last-first;
resize(size() + count);
memcpy(data+offset, first, count);
while ( first != last )
{
   *output++ = *first++;
}
另一方面,std::copybuffer、buffer+count、std::back_inserterv将实现为:

size_t count = last-first;
resize(size() + count);
memcpy(data+offset, first, count);
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通常会使用向量指令一次复制多个字节。

使用std::vector、v.insertv.end、buffer、buffer+count的良好实现;可实施为:

size_t count = last-first;
resize(size() + count);
memcpy(data+offset, first, count);
while ( first != last )
{
   *output++ = *first++;
}
另一方面,std::copybuffer、buffer+count、std::back_inserterv将实现为:

size_t count = last-first;
resize(size() + count);
memcpy(data+offset, first, count);
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通常会使用向量指令一次复制多个字节。

如果使用v.reserve怎么办?请澄清:您的问题是,如果禁用所有优化,为什么一个比另一个慢?因为在这种情况下,答案是这既不是不合理的,也不是出乎意料的。。。通过你的调试器来找出为什么你真的很好奇的原因。考虑提供,这样我们就可以重现你所面临的问题。我不确定它是否相关,但std::vector::push_通过std::back_插入器必须至少检查它是否每次都需要重新分配,即使您先调用reserve。std::vector::insert只需检查是否需要重新分配一次。对和的影响似乎更为严重。如果使用v.reserve怎么办?请澄清:您的问题是,如果禁用所有优化,为什么一个比另一个慢?因为在这种情况下,答案是这既不是不合理的,也不是出乎意料的。。。通过你的调试器来找出为什么你真的很好奇的原因。考虑提供,这样我们就可以重现你所面临的问题。我不确定它是否相关,但std::vector::push_通过std::back_插入器必须至少检查它是否每次都需要重新分配,即使您先调用reserve。std::vector::insert只需检查是否需要重新分配一次。对和的影响似乎更严重。看不到您正在链接的书:/I我尝试使用MSVC 2015编译此代码,启用和禁用优化,得到了相同的结果。@user463035818这只是一个离题笑话:这是一本德语逻辑书哲学家,他确实提出了这个问题,但得到了肯定的回答,因为人类的心理存在着相似的法则,这些法则必须是永恒的,科学和物理定律不变且基本。看不到你链接的那本书:/我试图用MSVC 2015编译这段代码,启用和禁用了优化,得到了相同的结果。@user463035818这只是一个离题的笑话:这是一本德国哲学家的逻辑书,他在书中提出了这个问题并回答了这个问题积极地说,既然存在着类似于人类心理的规律,那么这些规律必须与科学和物理定律一样永恒、不变和基本。v.push_真的比memcpy慢5倍吗?@AlexeyStarinsky如果是的话,我不会感到惊讶,在一个简单的推回背后可能隐藏着大量的代码。请重新考虑您的解释。请特别注意,这两个实现都没有调用除memmove和new等之外的任何函数,这两个函数都被使用。@MaxLanghof他没有使用clang,他在使用MSVCTrawling通过godbolt源代码插入最终调用std::_Copy_memmove with visualstudiois v.push_真的比memcpy慢5倍吗?@AlexeyStarinsky如果是这样的话,我不会感到惊讶,简单的push_后面可能隐藏着大量代码请重新考虑你的解释。请特别注意,两个实现都没有调用除memmove和new等之外的任何函数,这两个函数都使用。@MaxLanghof他没有使用clang,但他使用的是MSVCTrawling,通过godbolt源插入最终调用std::_Copy_memmove with visual studioreserve没有调用,因为向量的初始容量足够大。我有点不同意拷贝不能知道插入的东西。在最新的clang中,两个版本的实际复制都不依赖于memcpy,两个版本都使用了几个memmove,但其中一个版本在展开方面更具攻击性,或者我认为是这样。请注意部件的结构相似性。这都是模板化的代码,因此通过适当的内联和优化,无论是成员函数还是自由/泛型函数,这两个函数在理论上都应该能够实现相同的性能。@MaxLanghof-我期待看到您对报告的性能差异的解释。未调用reserve,因为向量的初始容量i
这已经够大了。我有点不同意复印件,因为我不知道可以插入的东西。在最新的clang中,两个版本的实际复制都不依赖于memcpy,两个版本都使用了几个memmove,但其中一个版本在展开方面更具攻击性,或者我认为是这样。请注意部件的结构相似性。这都是模板化的代码,因此通过适当的内联和优化,无论是成员函数还是自由/泛型函数,这两个函数在理论上都应该能够实现相同的性能。@MaxLanghof-我期待看到您对报告的性能差异的解释。