为什么省略push_back会使循环运行变慢?
Why does omitting the push_back make the loop run slower?
考虑这个程序,我在Cygwin上使用gcc 5.4.0和命令行g++ -std=c++14 -Wall -pedantic -O2 timing.cpp -o timing
编译。
#include <chrono>
#include <iostream>
#include <string>
#include <vector>
std::string generateitem()
{
return "a";
}
int main()
{
std::vector<std::string> items;
std::chrono::steady_clock clk;
auto start(clk.now());
std::string item;
for (int i = 0; i < 3000000; ++i)
{
item = generateitem();
items.push_back(item); // *********
}
auto stop(clk.now());
std::cout
<< std::chrono::duration_cast<std::chrono::milliseconds>
(stop-start).count()
<< " msn";
}
我一直得到大约500毫秒的报告时间。但是,如果我注释掉星号行,从而将push_back
省略到vector
,则报告的时间约为700 ms.
为什么不推到vector
使循环运行更慢?
我现在已经运行了测试,问题是在push_back
版本中,item
字符串没有被释放。将代码更改为:
#include <chrono>
#include <iostream>
#include <string>
#include <vector>
std::string generateitem()
{
return "a";
}
int main()
{
std::chrono::steady_clock clk;
auto start(clk.now());
{
std::vector<std::string> items;
std::string item;
for (int i = 0; i < 3000000; ++i)
{
item = generateitem();
items.push_back(item); // *********
}
}
auto stop(clk.now());
std::cout
<< std::chrono::duration_cast<std::chrono::milliseconds>
(stop-start).count()
<< " msn";
}
给出了在我的CygWin机器上两个选项大约相同时间的预期行为,因为我们这次测量了所有的释放。
为了进一步解释,原始代码基本上是:allocate items
start clock
repeat 3000000 times
allocate std::string("a")
move std::string("a") to end of items array
stop clock
deallocate 3000000 strings
所以,性能是由3000000个分配决定的。现在,如果我们注释掉push_back()
,我们得到:
allocate items
start clock
repeat 3000000 times
allocate std::string("a")
deallocate std::string("a")
stop clock
现在我们测量了3000000个分配和3000000个释放,所以很明显,它实际上会慢一些。我建议将items
向量释放移到时间跨度中,这意味着我们可以使用push_back()
:
start clock
allocate items
repeat 3000000 times
allocate std::string("a")
move std::string("a") to end of items array
deallocate 3000000 strings
stop clock
或不含push_back()
:
start clock
allocate items
repeat 3000000 times
allocate std::string("a")
deallocate std::string("a")
deallocate empty array
stop clock
所以,这两种方式我们都测量了3000000个分配和释放,所以代码将花费基本相同的时间。
感谢Ken Y-N的回答,我现在可以给出我自己问题的完整答案了。
代码在一个为std::string
实现写时复制的标准库版本上再次编译。也就是说,当复制字符串时,字符串内容的缓冲区不重复,并且两个字符串对象使用相同的缓冲区。只有当其中一个字符串被写入时才会发生重复。因此,已分配字符串缓冲区的生命周期如下:
-
在
generateitem
函数中创建 -
它是由
generateitem
函数通过RVO产生的 -
分配给
item
。(这是一个移动操作,因为std::string
是临时的) -
调用
push_back
复制std::string
,但不复制缓冲区。现在有两个std::string
共享一个缓冲区 -
在循环的下一次迭代中,下一个字符串被移动到
item
中。现在,唯一使用缓冲区的std::string
对象是矢量中的对象。 -
当vector在
main
完成时被销毁时,所有缓冲区的引用计数降为0,因此它们被释放。
因此,在测量的时间内没有任何缓冲区被释放。
如果我们消除对push_back
的调用,那么步骤4就不会发生。在步骤5中,缓冲区的引用计数下降到0,因此在测量的时间内,它被释放。这就解释了为什么测量时间会增加。
现在,根据文档,GCC 5应该已经用一个不使用copy-on-write的新类替换了copy-on-write字符串类。但是,显然,Cygwin默认情况下仍然使用旧版本。如果我们将-D_GLIBCXX_USE_CXX11_ABI=1
添加到命令行中,我们将得到新的字符串类,这样我们就得到了我们期望的结果。
- 循环无限运行C++解决骑士之旅问题
- 我的代码运行良好,但在游戏循环中中断
- 如何根据用户在C++中的输入运行不同数量的 for 循环
- 我可以根据用户输入在运行时生成"循环"吗?
- 重新启动后,线程无法在 while 循环中再次运行
- 运行无限循环的最小二叉树问题
- 目标C++,如何在后台线程中使用运行循环?
- 如何在指定的时间内运行循环
- 苹果运行循环的回调方法
- 我怎样才能一直运行循环,直到按下键 (C++)
- 如何在C++中每次运行循环时将变量递增1`
- 如何在第一次运行循环时忽略代码的特定部分
- 如何在每次运行循环时更改for循环中的对象
- 应用程序在访问主运行循环时崩溃 (SIGABRT)
- osX :运行循环选项(创建事件点击时)
- 线程如何运行循环直到连接
- 在指定时间运行c++循环
- 在运行循环外声明、分配和释放线程局部指针变量
- 这是在c++中运行循环的有效方式吗?
- 如何在qt中使用gui对象名运行循环