【问题标题】:Efficient push_back of classes and structs类和结构的高效 push_back
【发布时间】:2010-10-29 18:40:19
【问题描述】:

我经常看到我的同事做第二个 sn-p。为什么是这样?我尝试添加打印语句来跟踪 ctors 和 dtors,但两者看起来相同。

    std::vector<ClassTest> vecClass1;
    ClassTest ct1;
    ct1.blah = blah // set some stuff
    ...
    vecClass1.push_back(ct1);

    std::vector<ClassTest> vecClass2;
    vecClass2.push_back(ClassTest());
    ClassTest& ct2 = vecClass2.back();
    ct2.blah = blah // set some stuff
    ...

PS。如果标题有误导性,我很抱歉。

编辑:

首先,感谢大家的回复。

我使用std::move 编写了一个小应用程序。结果让我感到惊讶,也许是因为我做错了什么......请有人解释一下为什么“快速”路径的表现要好得多。

#include <vector>
#include <string>
#include <boost/progress.hpp>
#include <iostream>

const std::size_t SIZE = 10*100*100*100;
//const std::size_t SIZE = 1;
const bool log = (SIZE == 1);

struct SomeType {
    std::string who;
    std::string bio;
    SomeType() {
        if (log) std::cout << "SomeType()" << std::endl;
    }
    SomeType(const SomeType& other) {
        if (log) std::cout << "SomeType(const SomeType&)" << std::endl; 
        //this->who.swap(other.who);
        //this->bio.swap(other.bio);
        this->who = other.who;
        this->bio = other.bio;
    }
    SomeType& operator=(SomeType& other) {
        if (log) std::cout << "SomeType::operator=()" << std::endl;
        this->who.swap(other.who);
        this->bio.swap(other.bio);
        return *this;
    }
    ~SomeType() {
        if (log) std::cout << "~SomeType()" << std::endl;
    }
    void swap(SomeType& other) {
        if (log) std::cout << "Swapping" << std::endl;
        this->who.swap(other.who);
        this->bio.swap(other.bio);
    }
        // move semantics
    SomeType(SomeType&& other) : 
          who(std::move(other.who))
        , bio(std::move(other.bio)) {
        if (log) std::cout << "SomeType(SomeType&&)" << std::endl;
    }
    SomeType& operator=(SomeType&& other) {
        if (log) std::cout << "SomeType::operator=(SomeType&&)" << std::endl;
        this->who = std::move(other.who);
        this->bio = std::move(other.bio);
        return *this;
    }
};

int main(int argc, char** argv) {

    {
        boost::progress_timer time_taken;
        std::vector<SomeType> store;
        std::cout << "Timing \"slow\" path" << std::endl;
        for (std::size_t i = 0; i < SIZE; ++i) {
            SomeType some;
            some.who = "bruce banner the hulk";
            some.bio = "you do not want to see me angry";
            //store.push_back(SomeType());
            //store.back().swap(some);
            store.push_back(std::move(some));
        }
    }
    {
        boost::progress_timer time_taken;
        std::vector<SomeType> store;
        std::cout << "Timing \"fast\" path" << std::endl;
        for (std::size_t i = 0; i < SIZE; ++i) {
            store.push_back(SomeType());
            SomeType& some = store.back();
            some.who = "bruce banner the hulk";
            some.bio = "you do not want to see me angry";
        }
    }
    return 0;
}

输出:

dev@ubuntu-10:~/Desktop/perf_test$ g++ -Wall -O3 push_back-test.cpp -std=c++0x
dev@ubuntu-10:~/Desktop/perf_test$ ./a.out 
Timing "slow" path
3.36 s

Timing "fast" path
3.08 s

【问题讨论】:

  • 过早的优化,我猜。
  • 请注意,在 C++ 中,“类和结构”是多余的,因为类和结构是同一个东西。
  • 关于您的编辑。首先要检查的事情——你的实现是否有一个有效的movestd::string,你的实现是否有std::vector::push_back(T&amp;&amp;)。如果任一答案为“否”,则 std::move 代码正在复制(因为您的实现的 C++11 支持不完整)。

标签: c++ stl stdvector


【解决方案1】:

如果在“设置一些东西”之后复制对象比之前更昂贵,那么如果在“设置一些东西”之前插入对象,则将对象插入向量时发生的复制将比之前更便宜之后。

实际上,由于您应该期望偶尔复制向量中的对象,因此这可能不是什么优化。

【讨论】:

  • 嗯...可能是这样,但是如果对象的复制成本很高,为什么要保存对象而不是指针(或shared_ptrs,就此而言)?
  • @Diego:有些人以“表演”的名义做最疯狂的事(恐吓引语很重要)
  • 就我个人而言,我很喜欢在构造函数中进行所有初始化,在这种情况下你就没有这个问题。您只需在调用 push_back 时将参数传递给构造函数。
【解决方案2】:

如果我们接受你同事的 sn-p 是明智的,因为 ClassTest 复制起来很昂贵,我更愿意:

using std::swap;

std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(ClassTest());
swap(ct1, vecClass1.back());

我认为它更清晰,并且可能更安全。 ... 代码可能会分配资源,因此可能会引发异常(或者是什么让完全构建的 ClassTest 复制起来如此昂贵?)。因此,除非向量确实是函数的本地向量,否则我认为在运行该代码时将其半构建并不是一个好主意。

当然,如果ClassTest 仅具有默认的swap 实现,这将更加昂贵,但如果ClassTest 没有高效的swap,那么它就没有复制成本高昂的业务。所以这个技巧也许应该只用于已知友好的类,而不是未知的模板参数类型。

正如 Gene 所说,std::move 无论如何都会更好,如果你有 C++0x 特性的话。

如果我们担心 ClassTest 的复制成本很高,那么重新定位向量是一个可怕的前景。所以我们也应该:

  • 在添加任何内容之前保留足够的空间,
  • 使用deque 而不是vector

【讨论】:

    【解决方案3】:

    第二个版本受益于移动临时。第一个版本是复制临时向量。所以第二个可能更快。第二个版本也可能具有更小的峰值内存需求,第一个版本创建两个对象,一个是临时对象,一个是副本,然后才删除临时对象。您可以通过显式移动临时来改进第一个版本:

    std::vector<ClassTest> vecClass1;
    ClassTest ct1;
    ct1.blah = blah // set some stuff
    ...
    vecClass1.push_back(std::move(ct1));
    

    【讨论】:

      【解决方案4】:

      您可能应该让您的同事知道确切原因,但我们仍然可以猜测。正如 James 指出的那样,如果对象在构造后复制成本更高,则效率可能会更高。

      我看到了两个版本的优势。

      我喜欢你同事的 sn-p 因为:虽然在这两种情况下都有 2 个对象,但在第二个版本中它们只共存了很短的时间。只有一个对象可以编辑:这避免了在push_back之后编辑ct1的潜在错误。

      我喜欢你的个人 sn-p,因为:调用 push_back 来添加第二个对象可能会使引用 ct2 无效,从而引发未定义行为的风险。第一个 sn-p 不存在这种风险。

      【讨论】:

        【解决方案5】:

        它们是相同的(据我所知)。也许他或她这样做是一种惯用的习惯。

        【讨论】:

          猜你喜欢
          • 2020-09-12
          • 1970-01-01
          • 1970-01-01
          • 2014-01-03
          • 1970-01-01
          • 2023-03-21
          • 2011-11-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多