C++ push_back против вставки против emplace
В настоящее время я делаю приложение, используя векторы с C++.
Я знаю, как предварительная оптимизация является корнем всего зла.
Но я действительно не могу не быть любопытным.
Я добавляю части других векторов в другой вектор.
Мы скажем, что вектор будет иметь размер, который никогда не изменится на 300.
Так как я всегда добавляю в конец вектора
Это быстрее сделать:a.reserve(300);
a.insert(a.end(), b.begin(), b.end());
или было бы быстрее перебрать вектор, который я хочу добавить и добавить каждый элемент отдельно (при этом резервируя заранее) с push_back
или же emplace
, (не уверен, что быстрее)
Кто-нибудь может мне помочь в этом?
3 ответа
Вот общий принцип: когда библиотека обеспечивает оба do_x_once
а также do_x_in_batch
тогда последний должен быть по крайней мере так же быстро, как do_x_once
в простой петле. Если это не так, то библиотека очень плохо реализована, поскольку для получения более быстрой версии достаточно простого цикла. Часто такие пакетные функции / методы могут выполнять дополнительную оптимизацию, потому что они знакомы с внутренними структурами данных.
Так, insert
должно быть по крайней мере так же быстро, как push_back
в петле. В этом конкретном случае, умная реализация insert
может сделать один reserve
для всех элементов, которые вы хотите вставить. push_back
придется проверять емкость вектора каждый раз. Не пытайтесь перехитрить библиотеку:)
Я предполагаю, что это действительно зависит от компилятора (реализация библиотеки), параметров компиляции и архитектуры. Выполнение быстрого теста в 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 с - Измените размер 'b', затем цикл for с помощью назначения индексов
b[i] = a[i];
: 0,264 сек - Без изменения размера "б", просто
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: 0,659 сек
- Цикл индекса: 0,482 с
- вставка: 0,210 с (без существенной разницы с резервом первым)
- std:: copy: 0,422 сек с резервированием первым. Получил bad_alloc без него.
- memcpy: 0,329 с
Интересно отметить, что с оптимизацией или без нее метод вставки масштабируется линейно. Другие методы были явно неэффективны без оптимизаций, но все равно не могли справиться с ними так быстро. Как заметил Джеймс Канзе, на g++ все по-другому. Запустите тест с вашей собственной платформой для проверки.
Как говорит larsmans, чем больше вы делаете в одном вызове библиотеки, тем выше вероятность того, что он будет более эффективным. В случае insert
в вектор библиотека обычно выполняет не более одного перераспределения и копирует каждый сдвинутый элемент не более одного раза. Если вы используете цикл и push_back
, он может перераспределяться несколько раз, что может быть значительно медленнее (например, на порядок).
Однако, в зависимости от типа, может быть быстрее сделать что-то вроде:
a.resize( 300 );
std::copy( b.begin(), b.end(), a.end() - 300 );
Я обнаружил, что это быстрее для простых скалярных типов (например,int
) с использованием g++ на машине Intel.