【问题标题】:How to take advantage of the Move Semantics for a better performance in C++11?如何利用 Move Semantics 在 C++11 中获得更好的性能?
【发布时间】:2017-02-27 16:32:05
【问题描述】:

经过多次尝试,我仍然不明白如何正确利用移动语义,以便不复制操作结果而只使用指针或 std::move 来“交换”指向的数据.这对于加速更复杂的函数(例如 f(g(),h(i(l,m),n(),p(q())) 非常有用 目标是:

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;
}

【问题讨论】:

标签: performance c++11 move-constructor move-assignment-operator


【解决方案1】:

您使用new 是一个危险信号,尤其是在std::vector 上。

std::vectors 原生支持移动语义。它们是一个内存管理类。内存管理类的手动内存管理是一个很大的危险信号。

遵循 0 的规则。=default 您的移动构造函数、移动赋值、复制构造函数、析构函数和复制赋值。从向量中删除*。不要分配它。将data-&gt; 替换为data.

你应该做的第二件事是改变:

MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {

就目前而言,您按值获取第一个参数。太好了。

但是一旦你看重它,你就应该重复使用它!返回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 等)。

【讨论】:

  • 谢谢@Yakk 实际上,即使我不知道手动处理指向向量的指针是一件危险的事情,但请你解释一下为什么? -,我确实开始寻找没有指向矢量的解决方案......无处可去。您的宝贵建议和提示确实非常完美!!!。非常感谢!!!!
  • @user2315094 这类似于将现代汽车放在手工制作的家用汽车上行驶。 vector 是一种管理内存的类型。这就是它的工作。它是一种工业级内存管理类型。您现在手动管理vector 本身。那就是您现在将精美的工厂制造的汽车放在上面的手工制作的家用汽车。这辆车已经可以四处行驶,但您将它放在了一个更糟糕的底盘上并驾驶它......没有充分的理由。 “DRY”是“不要重复自己”;在代码中,重复某些内容会吸引错误(如果您进行修复,则可能会在一个地方修复它)
  • 非常感谢 Yakk 的帮助和解释。马可
【解决方案2】:

调用scalarProduct1D时需要移动t1,否则会复制:

MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));

您需要显式使用std::move,因为t1 是一个左值 表达式。

请注意,如果您希望访问已移动的对象成为有效操作,则必须修复打印功能以避免取消引用 nullptr。相反,我建议避免对已移动对象进行有效的方法调用,因为它需要额外的检查并且不遵循 “此对象已被移动,现在它处于无效状态”的想法。

live wandbox example

【讨论】:

  • 嗨 Vittorio Romeo 这样做:MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));输出为:t3: 2, 4, 6, t1: Segmentation fault (core dumped)
  • @user2315094:是的,因为您在 printTensor 中取消引用 nullptr。移动后不要使用t1
  • 这让t1 处于糟糕的状态。我认为OP不应该这样做。仅当移动后根本不使用对象时才应移动它。
  • @RSahu:看看 OP 的目标。他预计t1 在移动后会变为“空”,但实际上他并没有在main 中移动它。移动后,只需在printTensor 中检查nullptr 即可实现目标,但我建议在移动后完全避免访问t1
  • @VittorioRomeo,我认为 OP 需要像 MTensor::Tensor1D t3(std::move(scalarProduct1D(t1,2))); 这样的东西。他们想要移动操作的结果
猜你喜欢
  • 2012-03-27
  • 1970-01-01
  • 2012-06-28
  • 2011-11-26
  • 2011-11-29
  • 2021-02-05
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
相关资源
最近更新 更多