【问题标题】:Is it more efficient to preallocate a vector?预分配向量是否更有效?
【发布时间】:2012-08-09 16:54:29
【问题描述】:

在 Stanley B.Lippman、Josee Lajoie 和 Barbara E. Moo 的 C++ Primer 第四版中,它指出:

因为向量的增长效率很高,所以通常最好让向量 通过动态添加元素来增长,因为元素值是 已知。

习惯于使用 c 或 java 的读者可能会想到,因为 vector 元素是连续存储的,最好预先分配 向量的预期大小。其实恰恰相反……

尽管我们可以在向量中预先分配给定数量的元素, 定义一个空向量并添加通常更有效 元素。

假设这是正确的(作者和他们一样有声望,一个是 C++ 本身的合著者)那么谁能给我一个证明这个说法的案例,并解释为什么?

【问题讨论】:

    标签: c++ stl


    【解决方案1】:

    视情况而定。

    如果您不知道最终大小是多少,则让向量使用其分配方案进行分配(通常每次加倍,或附近的某个地方)。这样可以避免为每个元素重新分配:

    std::vector<int> v;
    
    // good:
    for (/* populate v */) // unknown number of iterations
    {
        v.push_back(i); // possible reallocation, but not often
    }
    
    // bad:
    for (/* populate v */) // unknown number of iterations
    {
        v.reserve(v.size() + 1); // definite reallocation, every time
        v.push_back(i); // (no reallocation)
    }
    

    但如果您提前知道不会重新分配,那么请预先分配:

    std::vector<int> v;
    
    // good:
    v.reserve(10); 
    for (/* populate v */) // only 10 iterations (for example)
    {
        v.push_back(i); // no reallocations
    }
    
    // not bad, but not the best:
    for (/* populate v */) // only 10 iterations (for example)
    {
        v.push_back(i); // possible reallocation, but not often (but more than needed!)
    }
    

    【讨论】:

    • 我将在我原来的问题中添加更多上下文,但我认为这意味着即使预先知道大小,让向量增长通常更有效。
    • @dangerousdave:好的,但是如果您事先知道大小,只需分配一次即可。让它增长确实不是更好,这将不必要地经常分配。
    • 大多数情况下,差异会很小,没有人会注意到。那么少写代码效率更高。
    • @BoPersson:我认为这不是真的。内存分配通常不是轻量级的,通过重新分配完成的工作确实加起来,我在自己的应用程序中看到过。我当然假设我们谈论的是“真实”数量的数据,而不是微小的沙箱向量。
    • @BoPersson:我也是“不要过早优化”人群中的一员,但我在那个人群中的原因是因为人们经常浪费尝试进行甚至不在正确位置的优化的时间。如果进行这些优化不需要时间,那么我不会阻止它们。这种优化不需要时间,我希望任何有能力的程序员在可能的情况下预先分配一个缓冲区到所需的大小,这实际上是一行代码。这就像选择正确的排序实现,一个常规的设计决策,而不是尝试优化i++
    【解决方案2】:

    我为这个简单的例子计时:

    #include<iostream>
    #include<vector>
    
    int main() {
    
        int limit = 100 * 1000 * 1000;
        std::vector<long> my_vec;
        my_vec.reserve(limit); // comment out this line to not preallocate
    
        for (int i=0; i < limit; i++) {
            my_vec.push_back(i);
        }
    
        long my_sum = 0;
        for (int i=0; i < limit; i++) {
            my_sum += my_vec[i];
        }
    
        std::cout << my_sum << std::endl;
        return 0;
    }
    

    符合:

    g++ -std=c++11 -O2 my_file.cpp -o my_exec
    

    发现差异很大:

    没有预分配:

    real    0m3.366s
    user    0m1.656s
    sys     0m1.660s
    

    预分配:

    real    0m1.688s
    user    0m0.732s
    sys     0m0.936s
    

    我的结论是:如果构建向量是程序的重要组成部分,那么为了提高效率而进行预分配是有意义的。然而,一遍又一遍地构建一个更大的向量是不可能的,因此它很少是一个瓶颈。但是,使用reserve() 除了预分配之外还有其他优势。

    C++ 编程语言中的 Bjarne Stroustrup(第 4 次添加)有这样的说法:

    当我读到一个 向量。我惊讶地发现,对于我的所有用途, 调用reserve() 并没有显着影响性能。默认 增长策略和我的估计一样有效,所以我停止了 尝试使用reserve() 提高性能。相反,我用它来 提高重新分配延迟的可预测性并防止 指针和迭代器的失效。

    【讨论】:

      【解决方案3】:

      可以。它在很大程度上取决于元素是什么,复制或构造它们的工作量以及有多少。

      如果你预先分配了一个向量,你最终会为每个元素调用默认构造函数来生成空元素,然后稍后将项目复制到空间上。如果你添加元素,它可以复制或构建元素,这可能更有效。

      【讨论】:

      • 预分配不需要默认构造。 v.reserve(n); // constructs nothing
      • 但是,如果您使用reserve,它不会预先进行任何构造,而只会进行分配。哪个可能会更好。
      • @GManNickG:我认为引用,因为它说“预分配元素”而不是“预分配元素空间”,它指的是resize(或采用大小的构造函数),而不是reserve
      • 那里有相当多的语义歧义,所以我可以看到。不过,我认为更重要的是实际上应该做什么,而不是太在意作者的话的字面意思。
      • 澄清,通过“预分配”,考虑到上下文,我相信它指的是向量 svec(10,"hi")
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-17
      • 1970-01-01
      • 1970-01-01
      • 2017-06-26
      • 1970-01-01
      • 1970-01-01
      • 2019-01-29
      相关资源
      最近更新 更多