填充矢量时的效率

Efficiency when populating a vector

本文关键字:效率 填充      更新时间:2023-10-16

哪种效率更高,为什么?

vector<int> numbers;
for (int i = 0; i < 10; ++i)
    numbers.push_back(1);

vector<int> numbers(10,0);
for (int i = 0; i < 10; ++i)
    numbers[i] = 1;

感谢

最快的是:

vector <int> numbers(10, 1);

至于你的两种方法,通常是第二种;尽管第一个避免了构造函数中向量的第一次归零,但它从一开始就分配了足够的内存,避免了重新分配。

在我不久前做的基准测试中,即使在循环之前调用reserve,第二个方法也会获胜,因为push_back的开销(如果容量足够用于另一个项,则必须检查每个插入,并在必要时重新分配)仍然占第二个方法的归零开销的主导地位。

请注意,这适用于基元类型。如果您开始使用具有复杂复制构造函数的对象,通常性能最好的解决方案是reserve+push_back,因为您可以避免对默认构造函数的所有无用调用,这些调用通常比push_back的开销更重。

通常情况下,第二个更快,因为第一个可能涉及存储数据的底层数组的一个或多个重新分配。这可以通过reserve函数来缓解,比如

vector<int> numbers;
numbers.reserve(10);
for (int i = 0; i < 10; ++i)
    numbers.push_back(1);

这在性能上几乎接近第二个示例,因为reserve告诉向量为要添加的所有元素分配足够的空间,这样在for循环中就不会发生重新分配。然而,push_back仍然需要检查向量的大小是否超过其当前容量,并增加指示向量大小的值,因此这仍然比第二个示例稍慢。

通常,可能是第二种情况,因为push_back()可能会在循环过程中导致重新定位和调整大小,而在第二种情形中,您正在预先调整向量的大小。

使用第二个,如果您有可用的iota(C++11有),请使用它而不是for循环。

std::vector<int> numbers(10);
std::iota(numbers.begin(), numbers.end(), 0);

由于预分配内存,第二个更快。在代码的第一个变体中,您也可以使用numbers.reserve(10);,它会一次为您分配一些内存,而不是在每次迭代时(也许有些实现会进行更大的预留,但不要依赖于此)。

此外,您最好使用迭代器,而不是直接访问。因为迭代器操作更可预测,并且可以轻松地进行优化。

#include <algorithm>
#include <vector>
using namespace std;
staitc const size_t N_ELEMS = 10;
void some_func() {
  vector<int> numbers(N_ELEMS);
  // Verbose variant
  vector<int>::iterator it = numbers.begin();
  while(it != numbers.end())
    *it++ = 1;

  // Or more tight (using C++11 lambdas)
  // assuming vector size is adjusted
  generate(numbers.begin(), numbers.end(), []{ return 1; });
}

//

有一种中间情况,使用reserve()然后多次调用push_back()。如果您知道要插入多少元素,那么这将至少与仅调用push_back()一样有效。

调用reserve()而不是resize()的优点是,在您要向其写入内容之前,它不需要初始化成员。如果你有一个需要构造的类的对象向量,这可能会更昂贵,尤其是如果每个元素的默认构造函数不是平凡的,但即使这样也很昂贵。

不过,调用push_back的开销是,每次调用它时,它都需要根据容量检查当前大小,看看是否需要重新分配。

这是一个N个初始化与N个比较的例子。当类型为int时,很可能会对初始化(memset或其他什么)进行优化,使其更快,但对于对象,我想说比较(reserve和push_back)几乎肯定会更快。