【问题标题】:What is the most efficient way to append one std::vector to the end of another?将一个 std::vector 附加到另一个末尾的最有效方法是什么?
【发布时间】:2011-01-13 13:51:42
【问题描述】:

设v1为目标向量,v2需要附加到它的后面。

我现在在做:

v1.reserve(v1.size() + v2.size()); 
copy(v2.begin(), v2.end(), back_inserter(v1));

这是最有效的方法吗?或者它可以通过复制一块内存来完成吗? 谢谢!

【问题讨论】:

标签: c++ performance stl vector


【解决方案1】:

我只是用下面的代码做了一个快速的性能测量和

v1.insert( v1.end(), v2.begin(), v2.end() );

似乎是正确的选择(如上所述)。 不过,您可以在下面找到报告的性能。

测试代码:

#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("--------------------------------------------------------------\n");
    printf(" METHOD:  push_back()\n");
    printf("--------------------------------------------------------------\n");
    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("--------------------------------------------------------------\n");
    printf(" METHOD:  reserve() + push_back()\n");
    printf("--------------------------------------------------------------\n");
    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("--------------------------------------------------------------\n");
    printf(" METHOD:  insert()\n");
    printf("--------------------------------------------------------------\n");
    testdata.clear();
    { vector<int>().swap(testdata); }
    testdata = testdata1;
    {
        boost::timer::auto_cpu_timer t;

        testdata.insert( testdata.end(), testdata2.begin(), testdata2.end() );
    }

    printf("--------------------------------------------------------------\n");
    printf(" METHOD:  reserve() + insert()\n");
    printf("--------------------------------------------------------------\n");
    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("--------------------------------------------------------------\n");
    printf(" METHOD:  copy() + back_inserter()\n");
    printf("--------------------------------------------------------------\n");
    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("--------------------------------------------------------------\n");
    printf(" METHOD:  reserve() + copy() + back_inserter()\n");
    printf("--------------------------------------------------------------\n");
    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,输出如下:

--------------------------------------------------------------
 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%)

【讨论】:

    【解决方案2】:

    经过大量争论(以及 Matthieu M. 和 villintehaspam 的合理评论),我将把我的建议改为

    v1.insert( v1.end(), v2.begin(), v2.end() );
    

    我会保留以前的建议:

    v1.reserve( v1.size() + v2.size() ); 
    v1.insert( v1.end(), v2.begin(), v2.end() );
    

    后一种方式有一些理由,尽管它们都不够强大:

    • 无法保证重新分配向量的大小——例如如果总和大小为 1025,它可能会重新分配到 2048——取决于实现。 reserve 也没有这样的保证,但对于特定的实现,它可能是正确的。如果寻找瓶颈,检查它可能是合理的。
    • reserve 表明我们的意图很明确 - 在这种情况下优化可能更有效(reserve 可以在某些一流的实施中准备缓存)。
    • 另外,对于reserve,我们有一个C++ 标准保证只会有一次重新分配,而insert 的实现可能效率低下并进行多次重新分配(也可以通过特定实现进行测试)。

    【讨论】:

    • 保留很可能是不必要的,因为这可能会由插入函数自动完成。
    • @villintehaspam - 标准中还有 no 保证 insert 不会进行多次重新分配而不是一次。然而,对储备的保证:It is guaranteed that no reallocation takes place during insertions that happen after a call to reserve() until the time when an insertion would make the size of the vector greater than the size specified in the most recent call to reserve(). 因此,储备更安全。
    • @Kornel Kisielewicz:实际上我相信第 23.2.4.4 项中或多或少有这样的保证(对于普通迭代器),其中复杂性被指定为与范围内的元素数量成线性关系[first,last) 加上到向量末端的距离。除非我弄错了,否则多次重新分配不符合要求?
    • @ceretullis:比较在这里无关紧要,因为 OP 要求 v1 是目标,v2 附加到它上面。有时顺序很重要。另外,如果要比较,我宁愿检查容量而不是大小,看一个是否足够大以包含所有数据。如果您必须扩展一个的容量,无论如何您最终都会复制两者的内容,并且副本的顺序并不重要......
    • @Kornel:我可能不会使用reserve 并相信insert。也存在与复杂性的混淆。 push_back 由于使用的增长策略(通常是2*x+1)而具有摊销常数复杂性,因此即使使用多次重新分配insert 也会具有摊销线性复杂度。请注意,如果您插入的不是RandomAccessIterator,最好不要计算范围的长度,而只计算push_back...我不知道他们是否为random_access_iterator_tag 做了特殊情况。
    【解决方案3】:

    如果你有一个 pod 类型的向量,并且你真的需要性能,你可以使用 memcpy,它应该比向量更快.insert(...):

    v2.resize(v1.size() + v2.size());
    memcpy((void*)&v1.front(), (void*)&v2[v1.size()], sizeof(v1.front())*v1.size());
    

    更新: 虽然我只会在性能确实真的需要时使用它,但代码对于 pod 类型是安全的。

    【讨论】:

    • 虽然我不是 100% 确定,因为调整大小会产生很多不必要的构造函数调用。
    • @dirkgently:这就是我使用调整大小而不是保留的原因。
    • 是的,这非常快,但它使用了实现细节。
    • 可能更慢。它首先将内存设置为零,然后用正确的值覆盖它。现代 STL 实现会自动检测 POD,并且在复制它们时会生成 对齐 内存副本。因此,它在memcpy() 中保存了归零和对齐检查
    • 如果memcpy 真的很安全,那么实现可能会直接调用它。
    【解决方案4】:

    如果您碰巧使用 Boost,您可以下载 RangeEx 库 from the Boost Vault 的开发版本。这个库。不久前被 Boost 接受,但到目前为止它还没有与主要发行版集成。在其中你会发现一个新的基于范围的算法,它完全符合你的要求:

    boost::push_back(v1, v2);
    

    在内部它的工作方式类似于 UncleBens 给出的答案,但代码更简洁易读。

    【讨论】:

      【解决方案5】:

      使用专用方法可能更好更简单:vector.insert

      v1.insert(v1.end(), v2.begin(), v2.end());
      

      正如 Michael 所提到的,除非迭代器是输入迭代器,否则向量将计算出所需的大小并以线性复杂度一次性复制附加的数据。

      【讨论】:

      • 只要迭代器是正向、双向或随机访问的,向量的最终大小将预先确定并保留。所以没有必要预先执行reserve()。如果迭代器恰好是输入迭代器,则无法做到这一点,并且向量可能必须重新分配几次,具体取决于最终添加了多少项。
      • @Michael,请参阅我的回答以了解保留的原因。
      • @Kornel Kisielewicz:这是不正确的,reserve 也可能分配比需要更多的内存。
      • @villintehaspam - 可能,这就是我写“强烈提示”的原因
      • 我想这一切都归结为实施质量。我更愿意相信实现,因为我看不出该 reserve 应该有什么不同的技术原因。如果您的实现决定使用普通插入进行过度分配,则很可能使用保留进行过度分配。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-15
      • 2020-08-17
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 2020-03-23
      • 1970-01-01
      相关资源
      最近更新 更多