Ответ 1
Изменяет ли размер инициализацию вновь выделенного вектора, где резерв просто выделяет, но не создает?
Да.
Я делал быстрый тест производительности на блоке кода
void ConvertToFloat( const std::vector< short >& audioBlock,
std::vector< float >& out )
{
const float rcpShortMax = 1.0f / (float)SHRT_MAX;
out.resize( audioBlock.size() );
for( size_t i = 0; i < audioBlock.size(); i++ )
{
out[i] = (float)audioBlock[i] * rcpShortMax;
}
}
Я был доволен ускорением первоначальной очень наивной реализации, которая занимает всего более 1 мс для обработки 65536 образцов аудио.
Однако только для удовольствия я попробовал следующее
void ConvertToFloat( const std::vector< short >& audioBlock,
std::vector< float >& out )
{
const float rcpShortMax = 1.0f / (float)SHRT_MAX;
out.reserve( audioBlock.size() );
for( size_t i = 0; i < audioBlock.size(); i++ )
{
out.push_back( (float)audioBlock[i] * rcpShortMax );
}
}
Теперь я полностью ожидал, что это даст ту же производительность, что и исходный код. Однако внезапно цикл теперь принимает 900usec (то есть он на 100 секунд быстрее, чем другая реализация).
Может кто-нибудь объяснить, почему это даст лучшую производительность? Выполняет ли resize()
инициализацию вновь выделенного вектора, когда резерв просто выделяет, но не создает? Это единственное, о чем я могу думать.
PS это было протестировано на одном сердечнике 2Ghz AMD Turion 64 ML-37.
Изменяет ли размер инициализацию вновь выделенного вектора, где резерв просто выделяет, но не создает?
Да.
Первый код записывается в out[i]
, который сводится к begin() + i
(т.е. добавлению). Второй код использует push_back
, который, вероятно, сразу же записывается в известный указатель, эквивалентный end()
(т.е. без добавления). Вероятно, вы можете сделать первый запуск так же быстро, как второй, используя итераторы, а не целую индексацию.
Изменить: также для уточнения некоторых других комментариев: вектор содержит поплавки, а построение float эффективно не-op (так же, как объявление "float f;" не испускает код, только говорит компилятору, чтобы сохранить место для поплавка в стеке). Поэтому я считаю, что любая разница в производительности между resize()
и reserve()
для вектора поплавков не связана со строительством.
Изменение размера()
Изменяет контейнер так, чтобы он имел ровно n элементов, вставляя элементы в конец или стирая элементы с конца, если это необходимо. Если какие-либо элементы вставлены, они являются копиями t. Если n > a.size()
, это выражение эквивалентно a.insert(a.end(), n - size(), t)
. Если n < a.size()
, это эквивалентно a.erase(a.begin() + n, a.end())
.
Резерв()
Если n меньше или равно capacity()
, этот вызов не действует. В противном случае это запрос на выделение дополнительной памяти. Если запрос успешный, то capacity()
больше или равно n; в противном случае capacity()
не изменяется. В любом случае size()
не изменяется.
Память будет перераспределена автоматически, если в вектор будет добавлено больше элементов capacity() - size()
. Перераспределение не меняет size()
и не изменяет значения каких-либо элементов вектора. Однако он увеличивает capacity()
Резерв вызывает перераспределение вручную. Основной причиной использования reserve()
является эффективность: если вы знаете, в какой степени ваш вектор должен в конечном итоге расти, тогда обычно более эффективно распределять эту память одновременно, а не полагаться на автоматическую схему перераспределения.
out.resize( audioBlock.size() );
Так как out
size (= 0) меньше, чем audioBlock.size()
, дополнительные элементы создаются и добавляются в конец out
. Это создает новые элементы, вызывая их конструктор по умолчанию.
Резерв выделяет только память.