如何利用移动语义在 C++11 中获得更好的性能?
How to take advantage of the Move Semantics for a better performance in C++11?
经过多次尝试,我仍然不明白如何正确利用移动语义,以便不复制操作结果而只使用指针,或 std::move,到 "exchange" 指向的数据。这对于加速更复杂的函数非常有用,例如 f(g(),h(i(l,m),n(),p(q()))
objective 是有:
t3={2,4,6};
t1={}; // empty
执行下面的代码时输出为:
t3={2,4,6};
t1={1,2,3};
代码:
namespace MTensor {
typedef std::vector<double> Tensor1DType;
class Tensor1D {
private:
//std::shared_ptr<Tensor1DType> data = std::make_shared<Tensor1DType>();
Tensor1DType * data = new Tensor1DType;
public:
Tensor1D() {
};
Tensor1D(const Tensor1D& other) {
for(int i=0;i<other.data->size();i++) {
data->push_back(other.data->at(i));
}
}
Tensor1D(Tensor1D&& other) : data(std::move(other.data)) {
other.data = nullptr;
}
~Tensor1D() {
delete data;
};
int size() {
return data->size();
};
void insert(double value) {
data->push_back(value);
}
void insert(const std::initializer_list<double>& valuesList) {
for(auto value : valuesList) {
data->push_back(value);
}
}
double operator() (int i) {
if(i>data->size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
return data->at(i);
}
Tensor1D& operator=(Tensor1D&& other) {
if (this == &other){
return *this;
}
data = other.data;
other.data = nullptr;
return *this;
}
void printTensor(Tensor1DType info) {
for(int i=0;i<info.size();i++) {
std::cout << info.at(i) << "," << std::endl;
}
}
void printTensor() {
for(int i=0;i<data->size();i++) {
std::cout << data->at(i) << "," << std::endl;
}
}
};
} // end of namespace MTensor
在文件 main.cpp 中:
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
MTensor::Tensor1D tensor;
for(int i=0;i<t1.size();++i) {
tensor.insert(t1(i) * scalar);
}
//return std::move(tensor);
return tensor;
}
int main() {
MTensor::Tensor1D t1;
t1.insert({1,2,3});
std::cout << "t1:" << std::endl;
t1.printTensor();
MTensor::Tensor1D t3(scalarProduct1D(t1,2));
std::cout << "t3:" << std::endl;
t3.printTensor();
std::cout << "t1:" << std::endl;
t1.printTensor();
return 0;
}
调用scalarProduct1D
时需要移动t1
,否则会复制:
MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
您需要显式使用 std::move
,因为 t1
是一个 lvalue 表达式。
请注意,如果您希望访问移出的对象成为有效操作,则必须修复打印函数以避免取消引用 nullptr
。相反,我建议避免对移出的对象进行有效的方法调用,因为它需要额外的检查并且不遵循 "this object has been moved, now it's in an invalid state".
的想法
您对 new
的使用是一个危险信号,尤其是在 std::vector
.
上
std::vector
s 原生支持移动语义。它们是内存管理class。内存管理的手动内存管理 class 是一个大红旗。
遵循 0 的规则。=default
你的移动构造函数、移动赋值、复制构造函数、析构函数和复制赋值。从向量中删除 *
。不要分配它。将 data->
替换为 data.
你应该做的第二件事是改变:
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
就目前而言,您按值获取第一个参数。太好了。
但是一旦你按价值使用它,你就应该重用它! Return t1
而不是创建一个新的临时文件并返回它。
为了提高效率,您需要一种就地修改张量的方法。
void set(int i, double v) {
if(i>data->size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
data.at(i) = v;
}
这给了我们:
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
for(int i=0;i<t1.size();++i) {
ts.set(i, t1(i) * scalar);
}
return t1; // implicitly moved
}
我们现在越来越接近了。
你要做的最后一件事是:
MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
将 t1
移动到 scalarProduct1D
。
您的代码的最后一个问题是您使用 at
和 检查边界。 at
的目的是检查边界。如果您使用 at
,请不要检查边界(使用 try/catch 进行检查)。如果检查边界,请使用 []
.
最终结果:
typedef std::vector<double> Tensor1DType;
class Tensor1D {
private:
//std::shared_ptr<Tensor1DType> data = std::make_shared<Tensor1DType>();
Tensor1DType data;
public:
Tensor1D() {};
Tensor1D(const Tensor1D& other)=default;
Tensor1D(Tensor1D&& other)=default;
~Tensor1D()=default;
Tensor1D& operator=(Tensor1D&& other)=default;
Tensor1D& operator=(Tensor1D const& other)=default;
Tensor1D(const std::initializer_list<double>& valuesList) {
insert(valuesList);
}
int size() const {
return data.size();
};
void insert(double value) {
data.push_back(value);
}
void insert(const std::initializer_list<double>& valuesList) {
data.insert( data.end(), valuesList.begin(), valuesList.end() );
}
double operator() (int i) const {
if(i>data.size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
return data[i];
}
void set(int i, double v) {
if(i>data->size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
data.at(i) = v;
}
static void printTensor(Tensor1DType const& info) {
for(double e : info) {
std::cout << e << "," << std::endl;
}
}
void printTensor() const {
printTensor(data);
}
};
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
for(int i=0;i<t1.size();++i) {
t1.set(i, t1(i) * scalar);
}
return t1;
}
int main() {
MTensor::Tensor1D t1 = {1,2,3};
std::cout << "t1:" << std::endl;
t1.printTensor();
MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
std::cout << "t3:" << std::endl;
t3.printTensor();
std::cout << "t1:" << std::endl;
t1.printTensor();
return 0;
}
还有一些其他的小修复(例如使用 range-for、DRY 等)。
经过多次尝试,我仍然不明白如何正确利用移动语义,以便不复制操作结果而只使用指针,或 std::move,到 "exchange" 指向的数据。这对于加速更复杂的函数非常有用,例如 f(g(),h(i(l,m),n(),p(q())) objective 是有:
t3={2,4,6};
t1={}; // empty
执行下面的代码时输出为:
t3={2,4,6};
t1={1,2,3};
代码:
namespace MTensor {
typedef std::vector<double> Tensor1DType;
class Tensor1D {
private:
//std::shared_ptr<Tensor1DType> data = std::make_shared<Tensor1DType>();
Tensor1DType * data = new Tensor1DType;
public:
Tensor1D() {
};
Tensor1D(const Tensor1D& other) {
for(int i=0;i<other.data->size();i++) {
data->push_back(other.data->at(i));
}
}
Tensor1D(Tensor1D&& other) : data(std::move(other.data)) {
other.data = nullptr;
}
~Tensor1D() {
delete data;
};
int size() {
return data->size();
};
void insert(double value) {
data->push_back(value);
}
void insert(const std::initializer_list<double>& valuesList) {
for(auto value : valuesList) {
data->push_back(value);
}
}
double operator() (int i) {
if(i>data->size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
return data->at(i);
}
Tensor1D& operator=(Tensor1D&& other) {
if (this == &other){
return *this;
}
data = other.data;
other.data = nullptr;
return *this;
}
void printTensor(Tensor1DType info) {
for(int i=0;i<info.size();i++) {
std::cout << info.at(i) << "," << std::endl;
}
}
void printTensor() {
for(int i=0;i<data->size();i++) {
std::cout << data->at(i) << "," << std::endl;
}
}
};
} // end of namespace MTensor
在文件 main.cpp 中:
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
MTensor::Tensor1D tensor;
for(int i=0;i<t1.size();++i) {
tensor.insert(t1(i) * scalar);
}
//return std::move(tensor);
return tensor;
}
int main() {
MTensor::Tensor1D t1;
t1.insert({1,2,3});
std::cout << "t1:" << std::endl;
t1.printTensor();
MTensor::Tensor1D t3(scalarProduct1D(t1,2));
std::cout << "t3:" << std::endl;
t3.printTensor();
std::cout << "t1:" << std::endl;
t1.printTensor();
return 0;
}
调用scalarProduct1D
时需要移动t1
,否则会复制:
MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
您需要显式使用 std::move
,因为 t1
是一个 lvalue 表达式。
请注意,如果您希望访问移出的对象成为有效操作,则必须修复打印函数以避免取消引用 nullptr
。相反,我建议避免对移出的对象进行有效的方法调用,因为它需要额外的检查并且不遵循 "this object has been moved, now it's in an invalid state".
您对 new
的使用是一个危险信号,尤其是在 std::vector
.
std::vector
s 原生支持移动语义。它们是内存管理class。内存管理的手动内存管理 class 是一个大红旗。
遵循 0 的规则。=default
你的移动构造函数、移动赋值、复制构造函数、析构函数和复制赋值。从向量中删除 *
。不要分配它。将 data->
替换为 data.
你应该做的第二件事是改变:
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
就目前而言,您按值获取第一个参数。太好了。
但是一旦你按价值使用它,你就应该重用它! Return t1
而不是创建一个新的临时文件并返回它。
为了提高效率,您需要一种就地修改张量的方法。
void set(int i, double v) {
if(i>data->size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
data.at(i) = v;
}
这给了我们:
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
for(int i=0;i<t1.size();++i) {
ts.set(i, t1(i) * scalar);
}
return t1; // implicitly moved
}
我们现在越来越接近了。
你要做的最后一件事是:
MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
将 t1
移动到 scalarProduct1D
。
您的代码的最后一个问题是您使用 at
和 检查边界。 at
的目的是检查边界。如果您使用 at
,请不要检查边界(使用 try/catch 进行检查)。如果检查边界,请使用 []
.
最终结果:
typedef std::vector<double> Tensor1DType;
class Tensor1D {
private:
//std::shared_ptr<Tensor1DType> data = std::make_shared<Tensor1DType>();
Tensor1DType data;
public:
Tensor1D() {};
Tensor1D(const Tensor1D& other)=default;
Tensor1D(Tensor1D&& other)=default;
~Tensor1D()=default;
Tensor1D& operator=(Tensor1D&& other)=default;
Tensor1D& operator=(Tensor1D const& other)=default;
Tensor1D(const std::initializer_list<double>& valuesList) {
insert(valuesList);
}
int size() const {
return data.size();
};
void insert(double value) {
data.push_back(value);
}
void insert(const std::initializer_list<double>& valuesList) {
data.insert( data.end(), valuesList.begin(), valuesList.end() );
}
double operator() (int i) const {
if(i>data.size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
return data[i];
}
void set(int i, double v) {
if(i>data->size()) {
std::cout << "index must be within vector dimension" << std::endl;
exit(1);
}
data.at(i) = v;
}
static void printTensor(Tensor1DType const& info) {
for(double e : info) {
std::cout << e << "," << std::endl;
}
}
void printTensor() const {
printTensor(data);
}
};
MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
for(int i=0;i<t1.size();++i) {
t1.set(i, t1(i) * scalar);
}
return t1;
}
int main() {
MTensor::Tensor1D t1 = {1,2,3};
std::cout << "t1:" << std::endl;
t1.printTensor();
MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
std::cout << "t3:" << std::endl;
t3.printTensor();
std::cout << "t1:" << std::endl;
t1.printTensor();
return 0;
}
还有一些其他的小修复(例如使用 range-for、DRY 等)。