What is the most efficient way to append one std::vector to the end of another?
假设v1是目标向量,v2需要附加到它的后面。
我现在正在做:
1 2 | v1.reserve(v1.size() + v2.size()); copy(v2.begin(), v2.end(), back_inserter(v1)); |
这是最有效的方法吗?或者可以通过复制一块内存来完成?谢谢!
经过多次争论(以及马修姆和维林泰哈斯潘的合理评论),我将把我的建议改为
1 | v1.insert( v1.end(), v2.begin(), v2.end() ); |
我将保留以前的建议:
1 2 | v1.reserve( v1.size() + v2.size() ); v1.insert( v1.end(), v2.begin(), v2.end() ); |
后一种方法有一些原因,尽管它们都不够强大:
- 无法保证将重新分配向量的大小——例如,如果总和大小为1025,则可能会根据实现情况重新分配到2048。对于
reserve 也没有这样的保证,但对于具体的实现,这可能是真的。如果寻找瓶颈的话,检查一下可能会很麻烦。 - reserve表明我们的意图是明确的——在这种情况下优化可能更有效(reserve可以在一些顶级实现中准备缓存)。
- 此外,对于EDCOX1,0,我们有一个C++标准保证只会有一个重新分配,而EDCOX1×2可能会被低效地执行,并做一些重新分配(也可以用一个特定的实现来测试)。
可能更好更简单地使用专用方法:vector.insert
1 | v1.insert(v1.end(), v2.begin(), v2.end()); |
正如Michael提到的,除非迭代器是输入迭代器,否则向量将计算出所需的大小,并以线性复杂性一次性复制附加的数据。
我只是用下面的代码做了一个快速的性能测量,
1 | v1.insert( v1.end(), v2.begin(), v2.end() ); |
似乎是正确的选择(如上所述)。不过,您可以在下面找到报告的性能。
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | #include <vector> #include <string> #include <boost/timer/timer.hpp> //============================================================================== // //============================================================================== /// Returns a vector containing the sequence [ 0, ... , n-1 ]. inline std::vector<int> _range(const int n) { std::vector<int> tmp(n); for(int i=0; i<n; i++) tmp[i] = i; return tmp; } void test_perf_vector_append() { const vector<int> testdata1 = _range(100000000); const vector<int> testdata2 = _range(100000000); vector<int> testdata; printf("-------------------------------------------------------------- "); printf(" METHOD: push_back() "); printf("-------------------------------------------------------------- "); testdata.clear(); { vector<int>().swap(testdata); } testdata = testdata1; { boost::timer::auto_cpu_timer t; for(size_t i=0; i<testdata2.size(); i++) { testdata.push_back(testdata2[i]); } } printf("-------------------------------------------------------------- "); printf(" METHOD: reserve() + push_back() "); printf("-------------------------------------------------------------- "); testdata.clear(); { vector<int>().swap(testdata); } testdata = testdata1; { boost::timer::auto_cpu_timer t; testdata.reserve(testdata.size() + testdata2.size()); for(size_t i=0; i<testdata2.size(); i++) { testdata.push_back(testdata2[i]); } } printf("-------------------------------------------------------------- "); printf(" METHOD: insert() "); printf("-------------------------------------------------------------- "); testdata.clear(); { vector<int>().swap(testdata); } testdata = testdata1; { boost::timer::auto_cpu_timer t; testdata.insert( testdata.end(), testdata2.begin(), testdata2.end() ); } printf("-------------------------------------------------------------- "); printf(" METHOD: reserve() + insert() "); printf("-------------------------------------------------------------- "); testdata.clear(); { vector<int>().swap(testdata); } testdata = testdata1; { boost::timer::auto_cpu_timer t; testdata.reserve( testdata.size() + testdata.size() ); testdata.insert( testdata.end(), testdata2.begin(), testdata2.end() ); } printf("-------------------------------------------------------------- "); printf(" METHOD: copy() + back_inserter() "); printf("-------------------------------------------------------------- "); testdata.clear(); { vector<int>().swap(testdata); } testdata = testdata1; { boost::timer::auto_cpu_timer t; testdata.reserve(testdata.size() + testdata2.size()); copy(testdata2.begin(), testdata2.end(), back_inserter(testdata)); } printf("-------------------------------------------------------------- "); printf(" METHOD: reserve() + copy() + back_inserter() "); printf("-------------------------------------------------------------- "); testdata.clear(); { vector<int>().swap(testdata); } testdata = testdata1; { boost::timer::auto_cpu_timer t; testdata.reserve(testdata.size() + testdata2.size()); copy(testdata2.begin(), testdata2.end(), back_inserter(testdata)); } } |
对于Visual Studio 2008 SP1、X64、发布模式/O2/LTCG,输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | -------------------------------------------------------------- METHOD: push_back() -------------------------------------------------------------- 0.933077s wall, 0.577204s user + 0.343202s system = 0.920406s CPU (98.6%) -------------------------------------------------------------- METHOD: reserve() + push_back() -------------------------------------------------------------- 0.612753s wall, 0.452403s user + 0.171601s system = 0.624004s CPU (101.8%) -------------------------------------------------------------- METHOD: insert() -------------------------------------------------------------- 0.424065s wall, 0.280802s user + 0.140401s system = 0.421203s CPU (99.3%) -------------------------------------------------------------- METHOD: reserve() + insert() -------------------------------------------------------------- 0.637081s wall, 0.421203s user + 0.218401s system = 0.639604s CPU (100.4%) -------------------------------------------------------------- METHOD: copy() + back_inserter() -------------------------------------------------------------- 0.743658s wall, 0.639604s user + 0.109201s system = 0.748805s CPU (100.7%) -------------------------------------------------------------- METHOD: reserve() + copy() + back_inserter() -------------------------------------------------------------- 0.748560s wall, 0.624004s user + 0.124801s system = 0.748805s CPU (100.0%) |
如果您碰巧使用Boost,那么可以从Boost保管库下载Rangex库的开发版本。这个库。不久前就被接受为Boost,但到目前为止还没有与主发行版整合。在其中,您将发现一种新的基于范围的算法,它可以完全满足您的需要:
1 | boost::push_back(v1, v2); |
在内部,它的工作方式与Unclebens给出的答案类似,但代码更简洁易读。
如果您有一个pod类型的向量,并且您确实需要它的性能,那么您可以使用memcpy,它应该比vector<>.insert(…):
1 2 | v2.resize(v1.size() + v2.size()); memcpy((void*)&v1.front(), (void*)&v2[v1.size()], sizeof(v1.front())*v1.size()); |
更新:尽管我只会在性能确实需要的情况下使用它,但是代码对于pod类型是安全的。