C++11-move语义的构建速度较慢

C++11 - move semantics is slow on construction

本文关键字:速度 构建 语义 C++11-move      更新时间:2023-10-16

此代码

#include <iostream>
#include <vector>
struct obj
{
std::string name;
int age;
float money;
obj():name("NO_NAME"),age(0),money(0.0f){}
obj(const std::string& _name, const int& _age, const float& _money):name(_name),age(_age),money(_money){}
obj(obj&& tmp): name(tmp.name), age(tmp.age), money(tmp.money) {}
obj& operator=(obj&&) {return *this;}
};
int main(int argc, char* argv[])
{
std::vector<obj> v;
for( int i = 0; i < 5000000; ++i )
{
v.emplace_back(obj("Jon", 45, 500.6f));
}
return(0);
}

比CCD_ 1的等效速度慢大约2倍,我不明白为什么。

我已经用机器人g++ 4.7.2clang++ 3.3对此进行了测试。

我哪里错了?


现在我已经更正了我的移动解释,我将添加更多

这是一个推送版本的

这是template_back版本

我正在Linux下用time实用程序测试这2,并用编译它们

g++-4.7 -std=c++11 -s -O3 -DNDEBUG

clang++ -std=c++11 -s -O3 -DNDEBUG

什么都不做更好。你试图让它更快(比什么更快?在你写移动构造函数之前,你真的对进行了评测吗?),但你破坏了它。

编译器免费生成复制和移动构造函数以及赋值运算符,而且她做得很好。通过决定自己编写,你就是在告诉编译器你更了解,所以她只是让开,让你改进自己打破它。

你打破的第一件事是,你让你的move构造函数实际上复制。有名称的事物是左值,即使它们是右值引用,也不能隐式地移动左值。因此初始化程序需要实际调用std::move

你破坏的第二件事是,你没有通过向move构造函数添加noexcept来声明它不抛出。由于没有声明没有抛出异常,std::vector的实现在重新分配底层存储时可能不会使用移动:如果没有移动不会抛出的保证,它就无法提供强大的异常保证。

这样做会让它表现得更好吗?大概也许不是。您的实现可能在std::string上进行小字符串优化,这意味着没有动态分配:整个字符串"Jon"很小,将直接存储在v.push_back(obj("Jon", 45, 500.6f));0对象中。这使得移动的成本与复制的成本相同。

通过动态分配并使用unique_ptr,可以使整个obj结构利用廉价的移动。这将使移动比复制更便宜,即使在进行小字符串优化的情况下也是如此。然而,你正在用分配成本和额外的间接性来为这种廉价付出代价。这是否可取,只有你自己才能判断。

您应该将数据从参数移动到移动构造函数:

obj(obj&& tmp)
: 
name(std::move(tmp.name)), age(std::move(tmp.age)), money(std::move(tmp.money)) {}

尽管如果正确使用emplace_back,这应该无关紧要。

而不是

v.emplace_back(obj("Jon", 45, 500.6f));

尝试

v.emplace_back("Jon", 45, 500.6f);

push_back具有启用移动的过载。emplace_back用于就地施工。

编辑:R.Martinho Fernandes说的话。:)

obj(obj&& tmp): name(std::move(tmp.name)), age(std::move(tmp.age)), money(std::move(tmp.money)) {}

这可能就是您想要的:

struct obj
{
std::string name;
int age;
float money;
obj()
: name("NO_NAME")
, age(0)
, money(0.0f)
{
}
obj(std::string _name, int _age, float _money)
: name(std::move(_name))
, age(std::move(_age))
, money(std::move(_money))
{
}
};
int main(int argc, char* argv[])
{
std::vector<obj> v;
for( int i = 0; i < 5000000; ++i )
{
v.emplace_back("Jon", 45, 500.6f);
}
return(0);
}

请注意,我更改了您的obj(std::string _name, int _age, float _money)构造函数以移动_name,而不是对其进行不必要的复制。

您也错误地调用了emplace_back,应该是emplace_back("Jon", 45, 500.6f)

所有其他内容都是由编译器自动优化生成的。

运行时间主要由字符串文本的std::字符串构造决定,因此move构造和template构造之间的区别很小。

这在我的机器上需要400毫秒:

#include <iostream>
#include <vector>
using namespace std;
struct obj
{
string name;
int age;
float money;
};
int main(int argc, char* argv[])
{
vector<obj> v;
for( int i = 0; i < 5000000; ++i )
{
v.emplace_back(obj{"Jon", 45, 500.6f});
}
return v.size();
}

这在我的机器上需要80毫秒:

#include <iostream>
#include <vector>
using namespace std;
struct obj
{
int age;
float money;
};
int main(int argc, char* argv[])
{
vector<obj> v;
for( int i = 0; i < 5000000; ++i )
{
v.emplace_back(obj{45, 500.6f});
}
return v.size();
}

请注意,一个普通结构将为其生成一个合理的默认移动构造函数

这已经在我的机器上花费了220毫秒:

#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
{
int t = 0;
for( int i = 0; i < 5000000; ++i )
{
string s("Jon");
t += s.size();
}
return t;
}