ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

c – 幕后的push_back()和emplace_back()

2019-09-10 14:06:55  阅读:220  来源: 互联网

标签:c c11 vector std move-semantics


我目前正在学习C语言,我很好奇push_back()和emplace_back()是如何工作的.当你试图构造并将一个大对象推送到容器的后面时,我总是假设emplace_back()更快,就像向量一样.

假设我有一个Student对象,我想要附加到学生矢量的背面.

struct Student {
   string name;
   int student_ID;
   double GPA;
   string favorite_food;
   string favorite_prof;
   int hours_slept;
   int birthyear;
   Student(string name_in, int ID_in, double GPA_in, string food_in, 
           string prof_in, int sleep_in, int birthyear_in) :
           /* initialize member variables */ { }
};

假设我调用push_back()并将Student对象推送到向量的末尾:

vector<Student> vec;
vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));

我的理解是push_back在向量之外创建一个Student对象的实例,然后将其移动到向量的后面.

图:https://ibb.co/hV6Jho

我也可以安抚而不是推动:

vector<Student> vec;
vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);

我的理解是,Student对象是在向量的最后面构造的,因此不需要移动.

图:enter image description here

因此,放置更快是有意义的,特别是如果添加了许多Student对象.但是,当我计算这两个版本的代码时:

for (int i = 0; i < 10000000; ++i) {
    vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));
}

for (int i = 0; i < 10000000; ++i) {
    vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);
}

我期望后者更快,因为不需要移动大的Student对象.奇怪的是,emplace_back版本最终变慢了(多次尝试).我还尝试插入10000000个Student对象,其中构造函数接受引用,push_back()和emplace_back()中的参数存储在变量中.这也没有用,因为安慰仍然较慢.

我已经检查过以确保在两种情况下都插入了相同数量的对象.时差不是太大,但安慰结束时间慢了几秒钟.

我对push_back()和emplace_back()如何工作的理解有什么问题吗?非常感谢您的宝贵时间!

根据要求,这是代码.我正在使用g编译器.

推回:

struct Student {
   string name;
   int student_ID;
   double GPA;
   string favorite_food;
   string favorite_prof;
   int hours_slept;
   int birthyear;
   Student(string name_in, int ID_in, double GPA_in, string food_in, 
           string prof_in, int sleep_in, int birthyear_in) :
           name(name_in), student_ID(ID_in), GPA(GPA_in), 
           favorite_food(food_in), favorite_prof(prof_in),
           hours_slept(sleep_in), birthyear(birthyear_in) {}
};

int main() {
    vector<Student> vec;
    vec.reserve(10000000);
    for (int i = 0; i < 10000000; ++i) 
         vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));
    return 0;
}

Emplace back:

struct Student {
   string name;
   int student_ID;
   double GPA;
   string favorite_food;
   string favorite_prof;
   int hours_slept;
   int birthyear;
   Student(string name_in, int ID_in, double GPA_in, string food_in, 
           string prof_in, int sleep_in, int birthyear_in) :
           name(name_in), student_ID(ID_in), GPA(GPA_in), 
           favorite_food(food_in), favorite_prof(prof_in),
           hours_slept(sleep_in), birthyear(birthyear_in) {}
};

int main() {
    vector<Student> vec;
    vec.reserve(10000000);
    for (int i = 0; i < 10000000; ++i) 
         vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);
    return 0;
}

解决方法:

此行为是由于std :: string的复杂性.这里有几件事情在互动:

>小字符串优化(SSO)
>在push_back版本中,编译器能够在编译时确定字符串的长度,而编译器无法为emplace_back版本执行此操作.因此,emplace_back调用需要调用strlen.此外,由于编译器不知道字符串文字的长度,它必须为SSO和非SSO情况发出代码(参见Jason Turner的“Initializer Lists Are Broken, Let’s Fix Them”;这是一个长篇大论,但他遵循插入字符串的问题整个矢量)

考虑这种更简单的类型:

struct type {
  std::string a;
  std::string b;
  std::string c;

  type(std::string a, std::string b, std::string c)
    : a{a}
    , b{b}
    , c{c}
  {}
};

请注意构造函数如何复制a,b和c.

Testing this against a baseline of just allocating memory,我们可以看到push_back优于emplace_back:

enter image description here

Click on image for quick-bench link

因为示例中的字符串都适合SSO缓冲区,所以复制与在这种情况下移动一样便宜.因此,构造函数非常有效,而emplace_back的改进效果较小.

另外,如果我们在the assembly搜索对push_back的调用和对emplace_back的调用:

// push_back call
void foo(std::vector<type>& vec) {
    vec.push_back({"Bob", "pizza", "Smith"});
}
// emplace_back call
void foo(std::vector<type>& vec) {
    vec.emplace_back("Bob", "pizza", "Smith");
}

(大会没有在这里复制.它很庞大.std :: string很复杂)

我们可以看到emplace_back调用了strlen,而push_back则没有.由于字符串文字和正在构造的std :: string之间的距离增加,编译器无法优化对strlen的调用.

显式调用std :: string构造函数会删除对strlen的调用,但不会再对其进行构造,因此无法加速emplace_back.

所有这些说,if we leave the SSO by using long enough strings,分配成本完全淹没了这些细节,因此emplace_back和push_back都具有相同的性能:

enter image description here

Click on image for quick-bench link

如果你修改了类型的构造函数来移动它的参数,那么emplace_back在所有情况下都会变得更快.

struct type {
  std::string a;
  std::string b;
  std::string c;

  type(std::string a, std::string b, std::string c)
    : a{std::move(a)}
    , b{std::move(b)}
    , c{std::move(c)}
  {}
};

SSO case

enter image description here

Click on image for quick-bench link

Long case

enter image description here

Click on image for quick-bench link

然而,SSO push_back案例放缓;编译器似乎发出了额外的副本.

optimal version of perfect forwarding没有这个缺点(请注意垂直轴上的刻度变化):

struct type {
  std::string a;
  std::string b;
  std::string c;

  template <typename A, typename B, typename C>
  type(A&& a, B&& b, C&& c)
    : a{std::forward<A>(a)}
    , b{std::forward<B>(b)}
    , c{std::forward<C>(c)}
  {}
};

enter image description here

Click on image for quick-bench link

标签:c,c11,vector,std,move-semantics
来源: https://codeday.me/bug/20190910/1799003.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有