С++ push_back vs Вставить vs emplace

В настоящее время я делаю приложение с использованием векторов с С++.

Я знаю, как предварительная оптимизация является корнем всего зла.

Но я действительно не могу не быть любопытным.

Я добавляю части других векторов в другой вектор.
Будем говорить, что вектор будет иметь размер, который никогда не изменится на 300.

Так как я всегда добавляю к концу векторного

Быстрее ли это сделать:
a.reserve(300);
a.insert(a.end(), b.begin(), b.end());

или будет быстрее прокручивать вектор, который я хочу добавить и добавить каждый элемент отдельно (при сохранении заранее) с помощью push_back или emplace. (неуверенный, который быстрее)

Кто-нибудь может мне помочь?

Ответ 1

Вот общий принцип: когда библиотека предоставляет как do_x_once, так и do_x_in_batch, последняя должна быть как минимум быстрее, чем вызов do_x_once в простой цикл. Если это не так, то библиотека очень плохо реализована, поскольку простой цикл достаточно для получения более быстрой версии. Часто такие пакетные функции/методы могут выполнять дополнительные оптимизации, поскольку они имеют знания о внутренних структурах данных.

Итак, insert должен быть не менее быстрым, чем push_back в цикле. В этом конкретном случае интеллектуальная реализация insert может сделать один reserve для всех элементов, которые вы хотите вставить. push_back нужно будет каждый раз проверять векторную емкость. Не пытайтесь перехитрить библиотеку:)

Ответ 2

Как говорит larsmans, чем больше вы делаете в одном вызове библиотеки, тем больше более вероятно, что он будет более эффективным. В случае insert в вектор, библиотека, как правило, делает не более одного перераспределение и скопировать каждый сдвинутый элемент не более одного раза. Если вы используете цикл и push_back, он может перераспределить несколько раз, что может быть значительно медленнее (например, порядок величина).

В зависимости от типа, однако, это может быть и быстрее сделать что-то вроде:

a.resize( 300 );
std::copy( b.begin(), b.end(), a.end() - 300 );

Я нашел, что это быстрее для простых скалярных типов (например, int), используя g++ на машине Intel.

Ответ 3

Я думаю, это действительно зависит от компилятора (реализация библиотеки), компиляции параметров и архитектуры. Выполнение быстрого теста в VS2005 без оптимизации (/Od) на Intel Xeon:

std::vector<int> a;
std::vector<int> b;

// fill 'a' with random values for giggles

timer.start()
// copy values from 'a' to 'b'
timer.stop()

Я получаю эти результаты за 10 000 000 элементов, используя эти разные методы "значений копирования...":

  • Запасное пространство для 'b', затем для цикла с использованием b.push_back(a[i]);: 0,808 с
  • Resize 'b', затем for-loop с использованием назначения индексов b[i] = a[i];: 0.264 sec
  • Нет повторной калибровки 'b', просто b.insert(b.end(), a.begin(), a.end());: 0,021 сек (без существенной разницы с запасом)
  • std::copy(a.begin(), a.end(), std::back_inserter(b));: 0,944 с (0,871 с запасом).
  • Измените размер 'b', затем memcopy на базовых указателях memcpy(&(b[0]), &(a[0]), 10000000*sizeof(int));: 0,061 сек

Однако, если оптимизация включена (/Ox), это другая история. Я должен был увеличить размер до 100 000 000, чтобы получить большую дифференциацию:

  • push_back loop: 0.659 сек
  • индексный цикл: 0.482 сек
  • вставка: 0.210 с (без существенной разницы с запасом)
  • std:: копия: 0.422 с с резервом в первую очередь. Получил bad_alloc без него.
  • memcpy: 0.329 sec

Интересно отметить, что с оптимизацией или без нее метод вставки масштабируется линейно. Другие методы были явно неэффективны без оптимизаций, но все же не могли с ними справиться. Как отметил Джеймс Канзе, он отличается от g++. Запустите тест с собственной платформой для проверки.