Я собираюсь ответить на вопрос, который, я думаю, вы действительно хотели задать, а именно: «Следует ли push_back()
избегать во внутренних циклах тяжелых алгоритмов?" а не то, что другие, кажется, прочитали в вашем посте, а именно: «имеет ли значение, если я вызову push_back перед выполнением несвязанной сортировки на большом векторе?» Кроме того, я собираюсь ответить из своего опыта, а не тратить время на поиск цитат и рецензируемых статей.
Ваш пример в основном делает две вещи, которые увеличивают общую стоимость процессора: он читает и работает с элементами входного вектора, а затем должен вставить элементы в выходной вектор. Вы обеспокоены стоимостью вставки элементов, потому что:
- push_back () - это постоянное время (действительно, мгновенное), когда у вектора достаточно места, предварительно зарезервированного для дополнительного элемента, но медленное, когда у вас заканчивается зарезервированное пространство.
- Выделение памяти обходится дорого (
malloc()
просто медленно , даже когда педанты делают вид, что new
- это нечто иное)
- Копирование векторных данных из одного региона в другой после перераспределения также происходит медленно : когда push_back () обнаруживает, что ему не хватает места, он должен пойти и выделить больший вектор, затем скопируйте все элементы . (Теоретически для векторов, размер которых много страниц ОС, волшебная реализация STL могла бы использовать VMM для перемещения их по виртуальному адресному пространству без копирования & mdash; на практике я никогда не видел такой, которая могла бы .) * * тысяча двадцать-одна
- Чрезмерное распределение выходных векторов вызывает проблемы: это вызывает фрагментацию, делая будущие распределения медленнее; он сжигает кеш данных, делая все медленнее; если он постоянен, он связывает дефицит свободной памяти, что приводит к подкачке диска на ПК и сбою на встроенных платформах.
- Недостаточное распределение выходных векторов вызывает проблемы, поскольку перераспределение вектора является операцией O (n), поэтому перераспределение его m раз равно O (m & times; n). Если распределитель по умолчанию STL использует экспоненциальное перераспределение (делая резерв вектора в два раза больше его предыдущего размера каждый раз, когда вы перераспределяете), это делает ваш линейный алгоритм O (n + n log m).
Следовательно, ваш инстинкт верен: всегда предварительно резервируйте пространство для векторов, где это возможно, не потому, что push_back медленный, а потому, что он может вызвать перераспределение, которое медленное . Кроме того, если вы посмотрите на реализацию shrink_to_fit
, вы увидите, что она также перераспределяет копии, временно удваивая ваши затраты памяти и вызывая дальнейшую фрагментацию.
Ваша проблема в том, что вы не всегда точно знаете, сколько места вам понадобится для выходных векторов; Обычный ответ - использовать эвристический и, возможно, пользовательский распределитель. Зарезервируйте n / 2 + k входного размера для каждого из ваших выходных векторов по умолчанию, где k - некоторый запас прочности. Таким образом, у обычно будет достаточно места для вывода, если ваш вход разумно сбалансирован, а push_back может перераспределить в редких случаях, когда это не так. Если вы обнаружите, что экспоненциальное поведение push_back тратит слишком много памяти (в результате чего вам нужно зарезервировать 2n элементов, когда на самом деле вам просто нужно n + 2), вы можете назначить ему собственный распределитель, который увеличивает размер вектора на меньшие линейные куски & mdash; но, конечно, это будет намного медленнее в случаях, когда векторы действительно не сбалансированы, и вы в конечном итоге делаете много изменений.
Нет способа всегда зарезервировать точное правильное количество места, не пройдя заранее элементы ввода; но если вы знаете, как выглядит баланс , обычно , вы можете использовать эвристику, чтобы сделать правильное предположение о статистическом выигрыше в производительности за многие итерации.