Как воспользоваться семантикой Move для повышения производительности в C++11?
После многих испытаний я все еще не понимаю, как правильно использовать семантику перемещения, чтобы не копировать результат операции и просто использовать указатель или 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;
}
2 ответа
Ваше использование new
это красный флаг, особенно на std::vector
,
std::vector
Поддержка движений семантики изначально. Это класс управления памятью. Ручное управление памятью класса управления памятью - БОЛЬШОЙ красный флаг.
Следуйте правилу 0. =default
ваш конструктор перемещения, назначение перемещения, конструктор копирования, деструктор и назначение копирования. Удалить *
из вектора. Не выделяй это. замещать data->
с 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 и т. д.).
Вы должны двигаться t1
при звонке scalarProduct1D
иначе вы сделаете копию:
MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
Вам нужно явно использовать std::move
так как t1
является выражением lvalue.
Обратите внимание, что вам придется исправить свои функции печати, чтобы избежать разыменования nullptr
если вы хотите, чтобы доступ к перемещенному объекту был допустимой операцией. Вместо этого я предлагаю не делать допустимым вызов метода для перемещенных объектов, поскольку он требует дополнительных проверок и не следует идее "этот объект был перемещен, теперь он находится в недопустимом состоянии".