我注意到有时程序运行速度很慢,但后来性能很好。例如,我有一些代码,我在循环中运行,第一次迭代需要很长时间,但相同代码的其他迭代运行得非常快。很难说明情况,因为我无法弄明白,甚至单个文字似乎也会影响这种行为。我准备了一个小代码片段:
#include <chrono>
#include <vector>
#include <iostream>
using namespace std;
int main()
{
const int num{ 100000 };
vector<vector<int>> octs;
for (int i{ 0 }; i < num; ++i)
{
octs.emplace_back(vector<int>{ 42 });
}
vector<int> datas;
for (int i{ 0 }; i < num; ++i)
{
datas.push_back(42);
}
for (int n{ 0 }; n < 10; ++n)
{
cout << "start" << '\n';
//cout << 0 << "start" << '\n';
auto start = chrono::high_resolution_clock::now();
for (int i{ 0 }; i < num; ++i)
{
vector<int> points{ 42 };
}
auto end = chrono::high_resolution_clock::now();
auto time = chrono::duration_cast<chrono::milliseconds>(end - start);
cout << time.count() << '\n';
}
cin.get();
return 0;
}
前两个载体是必不可少的。至少使用Visual Studio。认为它们没有被使用,它们会影响性能。此外,调整它们也会产生性能影响(比如更改初始化顺序,删除push_back
并在构造函数中分配必要的大小)。但是这段代码给了我以下结果:
此外,对于vs2013,如果我取消注释行cout << 0 << "start" << '\n';
,性能问题就会消失,并且所有迭代都是相同的!
发生了什么事?
答案 0 :(得分:1)
对于前两个循环,可能最大的性能考虑因素是内存分配,以及将vector
内容复制到更大的缓冲区。在这种情况下,循环似乎“获得速度”的事实并不令人惊讶。
这是由于vector
类的实现细节。我们来看看documentation:
在内部,向量使用动态分配的数组来存储它们 元素。可能需要重新分配此数组才能增长 插入新元素时的大小,这意味着分配一个新元素 数组并将所有元素移动到它。这是相对昂贵的 在处理时间方面的任务,因此,矢量不重新分配 每次将一个元素添加到容器中。
相反,矢量容器可以分配一些额外的存储空间 适应可能的增长,因此容器可能有一个 实际容量大于严格需要包含的存储容量 它的元素(即它的大小)。 图书馆可以实现不同的 增长战略,以平衡内存使用和 重新分配,但无论如何,重新分配只应发生在 对数增长的间隔大小使插入 可以提供向量末尾的各个元素 摊销的常数时间复杂度(见push_back)。
因此,为您的vector
分配的实际内存可能远远超过您实际使用的内存。所以vector
只需要在vector
添加一个不适合当前缓冲区的新元素时进行昂贵的重新分配和复制。此外,由于它说重新分配应该只以对数增长的间隔发生,你可以预期vector
类每次需要重新分配时大约加倍缓冲区大小。但请注意,各种平台上的vector
实现都经过高度调整,以便最适合该类的最常见使用模式,这可能是您在工具链和平台上看到的不同性能的一个因素。
因此,您应该看到在前几次执行时循环速度很慢,然后在push_back
和emplace
操作需要执行更少的重新分配和副本以适应新元素时获得更快的速度。
所以我认为这是你可以用来推断你的前两个循环应该执行多长时间的主要事实。但是对于您的具体示例,由于程序的简单性,编译器可能会对它生成的代码采取一些自由。所以我们可以想象一个足够聪明的优化编译器可能会发现你的vector
只会增长到它在编译时知道的大小num
。这是我怀疑你最后一个循环的最大问题,这似乎是一个随意和无用的测试。例如,可以完全优化循环3中的嵌套循环。我认为这是您在不同编译器中看到不同运行时行为的主要原因。
如果您想了解真实故事,请查看编译器生成的汇编代码。