Почему emplace_back быстрее push_back?

Я думал, что emplace_back будет победителем, когда сделаю что-то вроде этого:

v.push_back(myClass(arg1, arg2));

потому что emplace_back построит объект сразу в векторе, а push_back сначала создаст анонимный объект, а затем скопирует его в вектор. Для получения дополнительной информации см. этот вопрос.

Google также предоставляет this и this questions.

Я решил сравнить их для вектора, который был бы заполнен целыми числами.

Вот код эксперимента:

#include <iostream>
#include <vector>
#include <ctime>
#include <ratio>
#include <chrono>

using namespace std;
using namespace std::chrono;

int main() {

  vector<int> v1;

  const size_t N = 100000000;

  high_resolution_clock::time_point t1 = high_resolution_clock::now();
  for(size_t i = 0; i < N; ++i)
    v1.push_back(i);
  high_resolution_clock::time_point t2 = high_resolution_clock::now();

  duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

  std::cout << "push_back took me " << time_span.count() << " seconds.";
  std::cout << std::endl;

  vector<int> v2;

  t1 = high_resolution_clock::now();
  for(size_t i = 0; i < N; ++i)
    v2.emplace_back(i);
  t2 = high_resolution_clock::now();
  time_span = duration_cast<duration<double>>(t2 - t1);
  std::cout << "emplace_back took me " << time_span.count() << " seconds.";
  std::cout << std::endl;

  return 0;
}

В результате emplace_back работает быстрее.

push_back took me 2.76127 seconds.
emplace_back took me 1.99151 seconds.

Почему? Ответ на первый связанный вопрос четко говорит о том, что разницы в производительности не будет.

Также пробовали с другими методами времени, но получили идентичные результаты.

[РЕДАКТИРОВАТЬ] В комментариях говорится, что тестирование с ints ничего не говорит и что push_back требует реф.

Я проделал тот же тест в приведенном выше коде, но вместо int у меня был класс A:

class A {
 public:
  A(int a) : a(a) {}
 private:
  int a;
};

Результат:

push_back took me 6.92313 seconds.
emplace_back took me 6.1815 seconds.

[РЕДАКТИРОВАТЬ.2]

Как сказал Денлан, я также должен изменить положение операций, поэтому я поменял их местами, и в обеих ситуациях (int и class A) emplace_back снова был победителем.

[РЕШЕНИЕ]

Я запускал код в debug mode, что делает измерения недействительными. Для сравнительного анализа всегда запускайте код в release mode.


person gsamaras    schedule 17.05.2014    source источник
comment
Итак, означает ли это, что вы не видите этих различий в производительности при использовании в режиме выпуска? У вас есть такие же цифры?   -  person talekeDskobeDa    schedule 21.10.2019
comment
Привет, @talekeDskobeDa, я выполнял этот код на своем старом мертвом ноутбуке, так что нет, извините.   -  person gsamaras    schedule 21.10.2019
comment
Я понимаю, что в принципе emplace_back был бы быстрее - уж точно не медленнее. Чего я не понимаю (и нигде не могу найти), так это того, почему даже в этом простейшем случае у вас есть, почему оптимизатор не производит тот же код. Любые идеи?   -  person Ben    schedule 22.11.2019
comment
Не совсем @Ben, но если вы хотите, вы можете опубликовать новый вопрос, со ссылкой на мой вопрос, задав именно это (если вы поделитесь им со мной, пожалуйста). :)   -  person gsamaras    schedule 22.11.2019


Ответы (1)


Ваш тестовый пример не очень полезен. push_back берет элемент контейнера и копирует / перемещает его в контейнер. emplace_back принимает произвольные аргументы и создает из них новый элемент контейнера. Но если вы передадите emplace_back единственный аргумент, уже имеющий тип элемента, вы все равно будете использовать конструктор копирования / перемещения.

Вот сравнение получше:

Foo x; Bar y; Zip z;

v.push_back(T(x, y, z));  // make temporary, push it back
v.emplace_back(x, y, z);  // no temporary, directly construct T(x, y, z) in place

Однако ключевое отличие состоит в том, что emplace_back выполняет явные преобразования:

std::vector<std::unique_ptr<Foo>> v;
v.emplace_back(new Foo(1, 'x', true));  // constructor is explicit!

Этот пример будет слегка надуманным в будущем, когда вы скажете v.push_back(std::make_unique<Foo>(1, 'x', true)). Впрочем, с emplace очень хороши и другие конструкции:

std::vector<std::thread> threads;
threads.emplace_back(do_work, 10, "foo");    // call do_work(10, "foo")
threads.emplace_back(&Foo::g, x, 20, false);  // call x.g(20, false)
person Kerrek SB    schedule 17.05.2014
comment
Я думаю, что ваш v.emplace_back(new Foo(1, 'x', true)); не безопасен для исключений, поскольку он создает новый Foo, а затем вызывает emplace_back, который может выбросить, оставляя Foo просочившимся. - person Ben; 22.11.2019
comment
@Ben: Это правда. Следовательно, std::make_unique, чтобы обеспечить безопасное и не слишком подробное решение. - person Kerrek SB; 24.11.2019