Какой контейнер STL я должен использовать для FIFO? - PullRequest
79 голосов
/ 12 августа 2009

Какой контейнер STL подойдет мне лучше всего? В основном у меня есть контейнер шириной 10 элементов, в котором я постоянно push_back новых элементов, а pop_front самого старого элемента (около миллиона раз).

В настоящее время я использую std::deque для этой задачи, но мне было интересно, будет ли std::list более эффективным, поскольку мне не нужно будет перераспределять себя (или, возможно, я принимаю std::deque за std::vector?). Или есть еще более эффективный контейнер для моих нужд?

P.S. Мне не нужен произвольный доступ

Ответы [ 6 ]

168 голосов
/ 12 августа 2009

Поскольку существует множество ответов, вы можете быть смущены, но подведем итог:

Используйте std::queue. Причина этого проста: это структура FIFO. Вы хотите FIFO, вы используете std::queue.

Это проясняет ваши намерения кому-либо еще, и даже вам самим. A std::list или std::deque нет. Список можно вставлять и удалять в любом месте, что не является тем, что должна делать структура FIFO, а deque может добавлять и удалять с любого конца, что также не может делать структура FIFO.

Вот почему вы должны использовать queue.

Теперь вы спросили о производительности. Во-первых, всегда помните это важное правило: Сначала хороший код, а производительность в последнюю очередь.

Причина этого проста: люди, которые стремятся к производительности раньше, чем чистота и элегантность, почти всегда заканчивают последними. Их код превращается в кашу, потому что они отказались от всего хорошего, чтобы действительно ничего не извлечь из него.

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

Все это говорит, std::queue это всего лишь адаптер. Он обеспечивает безопасный интерфейс, но использует другой контейнер внутри. Вы можете выбрать этот базовый контейнер, и это обеспечивает большую гибкость.

Итак, какой контейнер следует использовать? Мы знаем, что std::list и std::deque оба предоставляют необходимые функции (push_back(), pop_front() и front()), так как мы решим?

Во-первых, следует понимать, что выделение (и освобождение) памяти не является быстрым делом, как правило, потому что оно предполагает выход в ОС и обращение к нему с просьбой сделать что-то. list должен выделять память каждый раз, когда что-то добавляется, и освобождать ее, когда она исчезает.

A deque, с другой стороны, выделяется кусками. Он будет выделяться реже, чем list. Думайте об этом как о списке, но каждый кусок памяти может содержать несколько узлов. (Конечно, я бы посоветовал вам действительно узнать, как это работает .)

Итак, с этим одним deque должен работать лучше, потому что он не так часто работает с памятью. Смешанный с фактом, что вы обрабатываете данные постоянного размера, он, вероятно, не должен будет выделяться после первого прохода данных, тогда как список будет постоянно выделяться и освобождаться.

Второе, что нужно понять, это производительность кеша . Выход в ОЗУ медленный, поэтому, когда процессору это действительно необходимо, он извлекает максимальную выгоду из этого времени, забирая часть памяти обратно в кэш. Поскольку deque выделяется в блоках памяти, вполне вероятно, что доступ к элементу в этом контейнере приведет к тому, что ЦП также вернет остальную часть контейнера. Теперь любой дальнейший доступ к deque будет быстрым, поскольку данные находятся в кеше.

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

Итак, учитывая это, deque должен быть лучшим выбором. Вот почему это контейнер по умолчанию при использовании queue. Тем не менее, это все еще только (очень) образованное предположение: вам придется профилировать этот код, используя deque в одном тесте и list в другом, чтобы действительно знать наверняка.

Но помните: получите код, работающий с чистым интерфейсом, затем беспокойтесь о производительности.

Джон высказывает опасение, что перенос list или deque приведет к снижению производительности. Еще раз, он и я не можем сказать наверняка, не профилируя это сами, но есть вероятность, что компилятор встроит вызовы, которые делает queue. То есть, когда вы говорите queue.push(), на самом деле он просто скажет queue.container.push_back(), полностью пропуская вызов функции.

Еще раз, это только обоснованное предположение, но использование queue не приведет к снижению производительности по сравнению с использованием базового контейнера raw. Как я уже говорил, используйте queue, потому что он чистый, простой в использовании и безопасный, и если он действительно становится проблемным профилем и тестирует.

27 голосов
/ 12 августа 2009

Выезд std::queue. Он упаковывает базовый тип контейнера, и контейнер по умолчанию - std::deque.

10 голосов
/ 22 января 2010

Если производительность действительно имеет значение, ознакомьтесь с библиотекой циклического буфера Boost .

7 голосов
/ 12 августа 2009

Я постоянно push_back новые элементы в то время как pop_front самый старый элемент (около миллиона раз).

Миллион - это на самом деле не так уж много. Как предлагали другие, используйте std::queue в качестве первого решения. В маловероятном случае, если он будет слишком медленным, определите узкое место, используя профилировщик (не догадайтесь!), И повторно внедрите, используя другой контейнер с тем же интерфейсом.

5 голосов
/ 12 августа 2009

Почему бы не std::queue? Все, что у него есть, это push_back и pop_front.

3 голосов
/ 12 августа 2009

A queue , вероятно, более простой интерфейс, чем deque , но для такого небольшого списка разница в производительности, вероятно, незначительна.

То же самое относится к списку . Это зависит только от выбора того, какой API вы хотите.

...