2009-11-16 4 views
6
inline void add(const DataStruct& rhs) { 
    using namespace boost::assign; 
    vec.reserve(vec.size() + 3); 
    vec += rhs.a, rhs.b, rhs.c; 
} 

выше функция была выполнена в течение приблизительно 17 000 раз, и это выполняется (насколько я могу видеть. Был некоторое преобразование участие) около 2 магнитуды хуже с ВЫЗОВ МАСТЕРА к вектору ::.станд :: вектор :: резерв снижения производительности

У меня всегда было впечатление, что резерв может ускорить push_back даже для небольших значений, но это не кажется правдой, и я не могу найти никаких очевидных причин, почему это не должно быть так. Сохраняет ли резерв возможность встраивания функции? Является ли вызов size() слишком дорогим? Это зависит от платформы? Я попытаюсь составить код небольшого теста, чтобы подтвердить это в чистой среде.

Компилятор: gcc (GCC) 4.4.2 с -g -O2

+1

Вы пытались зарезервировать место для 17000 * 3 входа? Могут быть некоторые накладные расходы с вашим дополнительным вызовом функции, что может объяснить разницу. – int3

+0

@splicer: Число было связано с testdata. Фактическое количество вызовов является переменной. – pmr

+0

моя точка зрения заключалась в том, что то, что вы делаете, эффективнее только с большими числами. Ответ Джеймса Шека дает вам возможность сделать это с помощью переменного количества вызовов, если вы знаете, с чего начать. в противном случае вам лучше разрешить реализацию по умолчанию для вас. – int3

ответ

24

реализация GCC из reserve() выделит точное количество элементов, в то время как push_back() будет расти внутренний буфер экспоненциально, удваивая его, так что вы разгромив экспоненциальный рост и принудительное перераспределение/копирование на каждой итерации. Запустите тест под ltrace или valgrind и просмотрите номер malloc() звонков.

+1

+1. Я только что проверил источники GCC и собирался написать то же самое. –

+0

Является ли экспоненциальный рост буфера через push_back() гарантированным стандартом или реализацией? – pmr

+0

Нет, экспоненциальный рост не гарантируется стандартом, это деталь реализации, такая же, как это конкретное поведение reserve(). –

6

Используйте только резерв, если вы знаете заранее, сколько места он будет использовать.

Reserve потребуется скопировать весь свой вектор ...

Если вы делаете push_back и вектор слишком мал, то он будет делать резервный (vec.size() * 2).

Если вы не знаете заранее, насколько велик ваш вектор, и если вам нужен произвольный доступ, рассмотрите возможность использования std :: deque.

+0

Это не эвристика. Доказано, что в среднем добавление нового элемента O (1). Прочтите любую книгу об амортизированном анализе (например, введение в алгоритмы). – Alexandru

+1

хорошо .. Я думаю, было бы справедливо сказать, что это эвристика, которая математически доказала свою лучшую производительность в среднем случае. – int3

+0

Вы можете доказать то же самое с умножением на 3 вместо двух. Но я все равно отредактирую, если это вас сильно беспокоит. –

7

Вы используете только reserve(), если знаете заранее количество элементов. В этом случае reserve() пространство для всех элементов сразу.

В противном случае просто используйте push_back() и полагайтесь на стратегию по умолчанию - она ​​будет перераспределять экспоненциально и значительно уменьшать количество перераспределений при стоимости немного субоптимальной памяти.

3

Переместить резерв за пределы добавления.

Каждый раз, когда вы вызываете «добавить», вы резервируете по крайней мере 3 дополнительных элемента. В зависимости от реализации вектора мог бы увеличивать размер резервного массива почти каждый раз, когда вы вызываете «добавить». То есть определенно будет вызывать разницу в производительности, которую вы описываете.

Правильный способ использования резерва является чем-то вроде:

vec.reserve(max*3); 
for(int i=0; i<max; i++) 
    add(i); 
3

Если вы профилировали код, я уверен, вы увидите, что + = IS очень быстро, проблема в том, что резерв убивает вас. Вы должны действительно использовать резерв, когда у вас есть какие-то знания о том, насколько большой вектор будет расти. Если вы догадаетесь заранее, сделайте ОДИН резерв, иначе просто перейдите по умолчанию push_back.

4

Когда std :: vector необходимо перераспределить, он увеличивает свой размер распределения на N * 2, где n - его текущий размер. Это приводит к логарифмическому числу reallocs по мере роста вектора.

Если вместо этого std :: vector увеличил свое выделенное пространство на постоянную величину, количество реаллоков будет расти линейно по мере роста вектора.

То, что вы сделали, по существу, приводит к тому, что вектор растет на постоянное количество 3, что означает линейный рост. Линейная, очевидно, хуже, чем логарифмическая, особенно с большими числами.

Как правило, единственный рост, отличный от логарифмического, является постоянным. Вот почему комитет по стандартам создал резервный метод. Если вы можете избежать всех reallocs (constant), вы будете работать лучше, чем логарифмическое поведение по умолчанию.

Это сказал, что вы можете рассмотреть комментарии Герба Саттера о предпочтя станд :: Deque над векторными www.gotw.ca/gotw/054.htm

Смежные вопросы