Подтвердить что ты не робот

Std::vector reserve() и push_back() быстрее, чем resize() и индекс массива, почему?

Я делал быстрый тест производительности на блоке кода

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.

4b9b3361

Ответ 1

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

Да.

Ответ 2

Первый код записывается в out[i], который сводится к begin() + i (т.е. добавлению). Второй код использует push_back, который, вероятно, сразу же записывается в известный указатель, эквивалентный end() (т.е. без добавления). Вероятно, вы можете сделать первый запуск так же быстро, как второй, используя итераторы, а не целую индексацию.

Изменить: также для уточнения некоторых других комментариев: вектор содержит поплавки, а построение float эффективно не-op (так же, как объявление "float f;" не испускает код, только говорит компилятору, чтобы сохранить место для поплавка в стеке). Поэтому я считаю, что любая разница в производительности между resize() и reserve() для вектора поплавков не связана со строительством.

Ответ 3

Изменение размера()

Изменяет контейнер так, чтобы он имел ровно 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() является эффективность: если вы знаете, в какой степени ваш вектор должен в конечном итоге расти, тогда обычно более эффективно распределять эту память одновременно, а не полагаться на автоматическую схему перераспределения.

Ответ 4

out.resize( audioBlock.size() );

Так как out size (= 0) меньше, чем audioBlock.size(), дополнительные элементы создаются и добавляются в конец out. Это создает новые элементы, вызывая их конструктор по умолчанию.

Резерв выделяет только память.