C ++ 11 — семантика перемещения медленная при построении

этот код

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

примерно в 2 раза медленнее, чем эквивалент с v.push_back(obj("Jon", 45, 500.6f)); и я не понимаю почему.

Я проверил это с ботом g++ 4.7.2 а также clang++ 3.3,

Где я не прав?


Теперь, когда я исправил свой ход, я добавлю больше

это версия push_back

это версия emplace_back

Я тестирую это 2 с time утилита под Linux и компиляция их с

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

или же

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

2

Решение

Ничего не делая лучше. Вы пытались сделать это быстрее (быстрее, чем что? Вы на самом деле профиль до Вы написали конструктор перемещения?), но вы сломали его.

Компилятор генерирует конструкторы копирования и перемещения и операторы присваивания бесплатно, и она делает это правильно. Решив написать свой собственный, вы сообщаете компилятору, что знаете лучше, поэтому она просто уходит с дороги и позволяет вам улучшать сломай это сам.

Первое, что вы сломали, это то, что вы сделали свой конструктор ходов на самом деле копия. Вещи с именем являются lvalues, и lvalue не может быть перемещена неявно, даже если они являются ссылками rvalue. Таким образом, инициализаторы должны на самом деле вызвать std::move,

Второе, что вы сломали, это то, что вы не заставили конструктор перемещения объявить, что он не генерирует, добавив noexcept к этому. Сгенерированный компилятор имел это. Не объявляя, что никакие исключения не выброшены, реализация std::vector вероятно, не будет использовать ходы при перераспределении основного хранилища: оно не может обеспечить гарантию сильных исключений без гарантии того, что ходы не генерируются.

Делает ли это все лучше? Может быть. Возможно, нет. Ваша реализация может делать небольшую строку оптимизации на std::string, и это означает, что нет динамического выделения: вся строка "Jon", будучи небольшим, будет храниться непосредственно в std::string объект. Это приводит к тому, что перемещение имеет те же затраты, что и копия.

Вы можете сделать целое obj структура использует преимущество дешевого движения, выделяя его динамически и используя unique_ptr, Это сделает ходы дешевле копий даже при небольшой оптимизации строки. Тем не менее, вы платите за эту дешевизну со стоимостью выделения и дополнительной косвенности. Желательно ли это или не только вы можете сказать.

4

Другие решения

Вы должны переместить данные из аргумента в конструктор перемещения:

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

Хотя это не имеет значения, если вы используете emplace_back правильно.

4

Вместо

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

пытаться

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

push_back имеет перегрузку с включенным перемещением. emplace_back для строительства на месте.

Изменить: что Р. Мартиньо Фернандес сказал. 🙂

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

Это, вероятно, то, что вы хотите:

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),

Все остальные вещи автоматически оптимально генерируются компилятором.

2

Во время выполнения преобладает конструкция std :: string из строкового литерала, поэтому разница между конструкцией move и конструкцией emplace тривиальна.

Это занимает 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;
}
2
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector