2012-10-02 3 views
7

Я просто узнал, что стандартный std-deque действительно медленный, если сравнивать с моей «самодельной» версией стека, которые используют предварительно выделенный массив.
Это код моего стека:std deque на удивление медленный

template <class T> 
class FastStack 
{  
public: 
    T* st; 
    int allocationSize; 
    int lastIndex; 

public: 
    FastStack(int stackSize); 
    FastStack(); 
    ~FastStack(); 

    inline void resize(int newSize); 
    inline void push(T x); 
    inline void pop(); 
    inline T getAndRemove(); 
    inline T getLast(); 
    inline void clear(); 
}; 

template <class T> 
FastStack<T>::FastStack() 
{ 
    lastIndex = -1; 
    st = NULL; 
} 

template <class T> 
FastStack<T>::FastStack(int stackSize) 
{ 
    st = NULL; 
    this->allocationSize = stackSize; 
    st = new T[stackSize]; 
    lastIndex = -1; 
} 

template <class T> 
FastStack<T>::~FastStack() 
{ 
    delete [] st; 
} 

template <class T> 
void FastStack<T>::clear() 
{ 
    lastIndex = -1; 
} 

template <class T> 
T FastStack<T>::getLast() 
{ 
    return st[lastIndex]; 
} 

template <class T> 
T FastStack<T>::getAndRemove() 
{ 
    return st[lastIndex--]; 
} 

template <class T> 
void FastStack<T>::pop() 
{ 
    --lastIndex; 
} 

template <class T> 
void FastStack<T>::push(T x) 
{ 
    st[++lastIndex] = x; 
} 

template <class T> 
void FastStack<T>::resize(int newSize) 
{ 
    if (st != NULL) 
     delete [] st; 
    st = new T[newSize]; 
} 

.
я запускаю этот простой тест для дека:

std::deque<int> aStack; 
int x; 

HRTimer timer; 
timer.Start(); 

for (int i = 0; i < 2000000000; i++) 
{ 
    aStack.push_back(i); 
    x = aStack.back(); 
    if (i % 100 == 0 && i != 0) 
     for (int j = 0; j < 100; j++) 
      aStack.pop_back(); 
} 

double totalTime = timer.Stop(); 
stringstream ss; 
ss << "std::deque " << totalTime; 
log(ss.str()); 

.
Использование зЬй стек с вектором в качестве контейнера (как 'Майкл Köhne' предложил)

std::stack<int, std::vector<int>> bStack; 
int x; 

HRTimer timer; 
timer.Start(); 

for (int i = 0; i < 2000000000; i++) 
{ 
    bStack.push(i); 
    x = bStack.top(); 
    if (i % 100 == 0 && i != 0) 
     for (int j = 0; j < 100; j++) 
      bStack.pop(); 
} 

double totalTime = timer.Stop(); 
stringstream ss; 
ss << "std::stack " << totalTime; 
log(ss.str()); 

.
И тот же один мой FastStack:

FastStack<int> fstack(200); 
int x; 

HRTimer timer; 
timer.Start(); 

for (int i = 0; i < 2000000000; i++) 
{ 
    fstack.push(i); 
    x = fstack.getLast(); 
    if (i % 100 == 0 && i != 0) 
     for (int j = 0; j < 100; j++) 
      fstack.pop(); 
} 

double totalTime = timer.Stop(); 
stringstream ss; 
ss << "FastStack " << totalTime; 
log(ss.str()); 

.
Результаты, полученные после 4 серий заключаются в следующем:
Deque 15,529
Deque 15,3756
Deque 15,429
Deque 15,4778

стек 6,19099
стек 6,1834
стек 6,19315
стек 6,19841

FastStack 3.01085
FastStack 2,9934
FastStack 3,02536
FastStack 3,00937

Результаты в секундах, и я был запущен код на 3570k Intel i5 (по часам по умолчанию). Я использовал компилятор VS2010 со всеми имеющимися оптимизациями. Я знаю, что у моего FastStack есть много ограничений, но есть много ситуаций, где он может быть использован, и когда он может дать хороший импульс! (Я использовал его в одном проекте, где я получаю 2x ускорение по сравнению с std :: queue).
Итак, теперь мой вопрос:
Есть ли в C++ другие «ингибиторы», которые все используют, но никто не знает о них?
EDIT: Я не хочу быть оскорбительным, мне просто интересно, если вы знаете некоторые неизвестные «ускорения», подобные этому.

+0

Вопрос о списке «ингибиторов», подобных этому, не является конструктивным. – nhahtdh

+2

После того, как вы построите deque, назовите [изменить размер] (http://www.cplusplus.com/reference/stl/deque/resize/). Посмотрите, если это делает большую разницу в скорости. Я предполагаю, что большая часть скорости, полученной/потерянной, принадлежит классу deque, который пытается «умно» управлять памятью, изменяя размер. –

+0

Вам не хватает фигурных скобок в инструкции if. – Rapptz

ответ

14

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

Проблема с производительностью является то, что std::deque будет выделять блоки по мере необходимости, и освободить их, когда они больше не нужны (после некоторой стратегии), так что если вы заполнить и очистить std::deque часто там будут распределения и deallocations непрерывно.

+0

Вы правы. Производительность становится лучше с 15 с до 6 с, но она по-прежнему очень медленная, когда сравнивается с простым классом «стек», как тот, который я делаю. Я знаю, что моя версия не имеет обработки ошибок или других функций, но она быстрее, а также позволяет произвольный доступ из-за публичных пользователей ... – icl7126

+2

@klerik: Какой компилятор и флаги вы используете? Единственная причина, по которой 'std :: vector' будет медленнее, чем ваш ручной код, - если вы используете проверенные итераторы или компилируете в режиме отладки без использования функции inlining. Тестирование производительности не является тривиальным, вы должны действительно понимать, что вы делаете, и последствия теста, иначе вы не сможете правильно интерпретировать результаты. –

+0

Я использовал VS2010, все тесты были запущены на Release build с параметрами/O2,/Oi,/Ot,/Oy-,/GL и/Ob2, поэтому любая подходящая функция должна быть встроенной, и все оптимизации кода должны работать. Я использовал debug только для определения емкости стека. Но я использовал проверенные итераторы, поэтому я отключил это сейчас, но похоже, что это не имеет никакого значения. – icl7126

21

Вы сравниваете яблоки и апельсины. Dequeue DOUBLE-ENDED, который требует, чтобы он был немного иным, чем простой стек, который вы реализовали. Попробуйте запустить свой тест против std::stack<int, std::vector<int> > и посмотрите, как вы это делаете. std :: stack - это контейнерный адаптер, и если вы используете вектор в качестве базового контейнера, он должен быть почти таким же быстрым, как и ваша домашняя реализация.

С одной стороны, реализация std :: stack не позволяет заранее установить размер, поэтому в ситуациях, когда вы знаете, какой максимальный размер должен быть, это будет сначала немного медленнее, поскольку он должен повторно назначать несколько раз. После этого он будет почти таким же.

+0

Тот же тест с использованием вектора в качестве контейнера для стека : 6.19099 6.1834 6.19315 6.19841. В то же время эталонный тест ставит только 100 номеров в стеке, поэтому перераспределение не должно быть такой большой проблемой. – icl7126

+0

Итак, выполните одну итерацию с помощью std :: stack (чтобы установить размер) и _then_ выполните тайм-цикл (используя тот же взрослое стек). Как это выглядит сейчас? – Useless

+0

Итак, я сделал один цикл и добавил 100 номеров, затем еще один цикл и удалил их (непосредственно перед строкой «Таймер HRTimer»). Используя отладчик, я проверяю пропускную способность стека после этих циклов, и он равен 141 и остается 141 все время (потому что в это же время добавлено только 100 элементов), а результаты хуже - 6.65823. Я сделал несколько экспериментов, поэтому я меняю петли до 1000, а затем результат был 6.34, а затем я изменил его на 1000000, а результат был 6.35 (емкость 1049869). Я не знаю, почему изменение результата при изменении неиспользуемого пространства емкости (я имею в виду разницу между 100 и 1000). – icl7126

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