向向量添加元素
有多种方法可以将数据添加到向量中:
- 创建一个空向量,
push_back()
元素融入其中。
- 创建一个空向量,分配一些容量
reserve()
, then push_back()
元素融入其中。
- 创建一个向量
n
元素,使用索引和复制分配。
- 创建一个空向量,
emplace_back()
元素融入其中。
- 创建一个空向量,分配一些容量
reserve()
, then emplace_back()
元素融入其中。
还有其他方法,例如使用一对迭代器创建容器,或通过标准库算法填充它。我不会在这里考虑这些。
一般考虑因素
以下两个考虑因素对于以下分析很重要:
-
避免(重新)分配:内存分配很慢。重新分配通常涉及将容器中已有的所有内容复制到新位置。
-
避免不必要的工作:构造你想要的元素比默认构造然后赋值更好。在您想要的地方构造一个元素比在其他地方构造它然后复制要好。
其他因素也会影响所选解决方案的效率,但这些是我们可以直接控制的重要因素。通过分析代码,其他因素可能会变得显而易见。
推回()
Each push_back()
从参数到向量复制构造一个元素push_back()
称呼。如果向量size() == capacity()
,将执行重新分配。这通常(但可能并不总是)使容量加倍,并可能导致复制all将现有元素放入新存储中。
预分配的push_back()
Using reserve()
在开始之前为元素分配足够的内存。如果您知道(或有合理的猜测)元素的数量,那么这样做总是值得的。如果你猜测的话,高估总比低估好。
The push_back()
call 仍将使用元素类型的复制构造函数,但不应进行任何分配,因为已经提供了空间。您只需支付期间单次分配的费用reserve()
称呼。如果您确实超出了现有容量,push_back()
将重新分配,通常将容量加倍。这就是为什么高估尺寸会更好;你不太可能得到重新分配,而如果低估的话,你不仅可能会重新分配,而且你会浪费几乎是你需要的两倍的内存分配!
请注意,“容量加倍”行为并未由标准指定,但它是一种常见的实现,旨在减少使用时的重新分配频率push_back()
对于未知大小的数据集。
索引和元素分配
在这里,我们创建一个由正确数量的默认构造元素组成的向量,然后使用复制赋值运算符将它们替换为我们想要的元素。它只有一次分配,但如果复制分配执行任何复杂的操作,则速度可能会很慢。这对于未知(或仅猜测)大小的数据集实际上不起作用;仅当您知道索引永远不会超过时,元素索引才是安全的size()
,你必须求助于push_back()
或者如果您需要更多则调整大小。这不是一个好的通用解决方案,但有时可以发挥作用。
emplace_back()
emplace_back()
使用参数就地构造元素emplace_back()
称呼。这通常比同等的速度更快push_back()
(但不总是)。它仍然以与push_back()
,保留一些容量,填充它,然后在需要更多容量时重新分配。大多数相同的论点都适用,但您可以从构造方法中获得一些收益。
emplace_back() 与预分配
这应该是 C++11 或更高版本代码库的首选策略。您将获得emplace_back()
效率(如果可能)并避免重复分配。在列出的机制中,这在大多数情况下预计是最快的。
关于效率的注释
效率可以通过多种方式来衡量。执行时间是一种常见的衡量标准,但并不总是最相关的。关于使用哪种策略的一般建议基于经验,本质上是“平均”各种效果,以提供一些关于首先做什么的合理陈述。与往常一样,如果任何类型的效率对您的应用程序至关重要,那么确保优化正确位置的唯一方法是profile您的代码,进行更改,然后profile再次证明这些变化达到了预期的效果。不同的数据类型、硬件、I/O 要求等都会影响这种时序,并且您永远不会知道这些影响如何在您的特定应用程序中结合起来,直到您profile it.
Example
实例:http://coliru.stacked-crooked.com/a/83d23c2d0dcee2ff http://coliru.stacked-crooked.com/a/83d23c2d0dcee2ff
在此示例中,我使用上面列出的每种方法用 10,000 个字符串填充向量。我对每一项进行计时并打印结果。
这和你的问题类似,但不完全相同;你的结果会有所不同!
请注意,emplace_back()
with reserve()
是最快的,但是这里的索引和分配也很快。这很可能是因为std::string
拥有高效的swap()
,并且它的默认构造函数没有做太多事情。其他方法要慢一个数量级。
#include <chrono>
#include <iostream>
#include <vector>
using Clock = std::chrono::high_resolution_clock;
using time_point = std::chrono::time_point<Clock>;
std::vector<std::string> strings = {"one", "two", "three", "four", "five"};
std::chrono::duration<double> vector_push_back(const size_t n) {
time_point start, end;
start = Clock::now();
std::vector<std::string> v;
for (size_t i = 0; i < n; ++i) {
v.push_back(strings[i % strings.size()]);
}
end = Clock::now();
return end - start;
}
std::chrono::duration<double> vector_push_back_with_reserve(const size_t n) {
time_point start, end;
start = Clock::now();
std::vector<std::string> v;
v.reserve(n);
for (size_t i = 0; i < n; ++i) {
v.push_back(strings[i % strings.size()]);
}
end = Clock::now();
return end - start;
}
std::chrono::duration<double> vector_element_assignment(const size_t n) {
time_point start, end;
start = Clock::now();
std::vector<std::string> v(n);
for (size_t i = 0; i < n; ++i) {
v[i] = strings[i % strings.size()];
}
end = Clock::now();
return end - start;
}
std::chrono::duration<double> vector_emplace_back(const size_t n) {
time_point start, end;
start = Clock::now();
std::vector<std::string> v;
for (size_t i = 0; i < n; ++i) {
v.emplace_back(strings[i % strings.size()]);
}
end = Clock::now();
return end - start;
}
std::chrono::duration<double> vector_emplace_back_with_reserve(const size_t n) {
time_point start, end;
start = Clock::now();
std::vector<std::string> v;
v.reserve(n);
for (size_t i = 0; i < n; ++i) {
v.emplace_back(strings[i % strings.size()]);
}
end = Clock::now();
return end - start;
}
int main() {
const size_t n = 10000;
std::cout << "vector push_back: " << vector_push_back(n).count() << "\n";
std::cout << "vector push_back with reserve: " << vector_push_back(n).count() << "\n";
std::cout << "vector element assignment: " << vector_element_assignment(n).count() << "\n";
std::cout << "vector emplace_back: " << vector_emplace_back(n).count() << "\n";
std::cout << "vector emplace_back with reserve: " << vector_emplace_back_with_reserve(n).count() << "\n";
}
Results:
vector push_back: 0.00205563
vector push_back with reserve: 0.00152464
vector element assignment: 0.000610934
vector emplace_back: 0.00125141
vector emplace_back with reserve: 0.000545451
结论
对于大多数新代码,使用reserve()
and emplace_back()
(or push_back()
对于较旧的代码)应该会给你一个很好的效率第一近似值。如果还不够,请对其进行分析并找出瓶颈所在。它可能不会在你想象的地方。