итерация упорядоченных и неупорядоченных контейнеров

Я хочу знать, какие структуры данных более эффективны для перебора их элементов между std::set, std::map и std::unordered_set, std::unordered_map.

Я просмотрел SO и нашел этот вопрос. В ответах предлагается либо скопировать элементы в std::vector, либо использовать Boost.Container, что, ИМХО, не отвечает на мой вопрос.

Моя цель состоит в том, чтобы хранить в контейнере большое количество уникальных элементов, которые большую часть времени я хочу перебирать. Вставки и извлечения встречаются реже. Я хочу избегать std::vector в сочетании с std::unique.


person 101010    schedule 21.07.2015    source источник
comment
Если итерация частая, вам очень, очень, очень нужен вектор.   -  person T.C.    schedule 21.07.2015
comment
@Т.С. Я знаю, но давайте притворимся, что я не могу использовать std::vector. Что лучше выбрать между упорядоченным и неупорядоченным и почему? :)   -  person 101010    schedule 21.07.2015
comment
@inf было бы неплохо, но я не могу использовать boost.   -  person 101010    schedule 21.07.2015
comment
@101010: В общем, на эти вопросы нельзя ответить, думая, и их можно решить только эмпирическим тестированием, и ответ может меняться в зависимости от самого компьютера, того, что еще работает на компьютере, реализации библиотеки, как работает контейнер. используется и так далее.   -  person    schedule 21.07.2015
comment
@101010: Я не могу сосчитать, как часто я это читал. Я не могу использовать Boost. Если Boost фактически не поддерживается на вашей целевой платформе, я не могу понять, кто придумывает такие ограничения. Boost — следующая лучшая вещь после стандартной библиотеки, и исключить его из проекта по принципиальным соображениям — это… ну… ‹/rant›, прежде чем я переступлю здесь границы. ;-)   -  person DevSolar    schedule 21.07.2015
comment
@DevSolar В моей компании мы не используем boost. Причина: Неизвестно...   -  person 101010    schedule 21.07.2015
comment
@101010: Скажем так, я надеюсь, что у вас достаточно авторитета, чтобы вынести эту тему на обсуждение. По крайней мере, попросите их сказать вам причину, потому что отказ от использования Boost для C++ немного похож на отказ от использования шаблонов проектирования в Java, потому что... неизвестно. ;-)   -  person DevSolar    schedule 21.07.2015
comment
@Hurkyl 'нельзя ответить, подумав' ну, это точно помогло бы подумать   -  person Nikos Athanasiou    schedule 21.07.2015
comment
@Nikos: Ах, я имел в виду, что нельзя ответить, думая в одиночку.   -  person    schedule 21.07.2015


Ответы (3)


Давайте рассмотрим set против unordered_set.

Основное отличие здесь заключается в «природе» итерации, то есть обход набора даст вам элементы по порядку, а обход диапазона в неупорядоченном наборе даст вам набор значений в произвольном порядке.

Предположим, вы хотите пройти через диапазон [it1, it2]. Если мы исключим время поиска, необходимое для поиска элементов it1 и it2, не может быть прямого сопоставления одного случая с другим, поскольку не гарантируется, что промежуточные элементы будут одинаковыми, даже если вы использовали одни и те же элементы для создания контейнера. .

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

Наборы обычно реализуются как красно-черные деревья (разновидность бинарных деревьев поиска). Как и все бинарные деревья поиска, они допускают эффективный обход по порядку (LRR: левый корень справа) своих элементов. То есть для прохождения вы платите за погоню за указателем (точно так же, как при обходе списка).

типичное красно-черное дерево

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

схема хеширования с цепочкой

В любом случае микронастройка и бенчмаркинг дадут вам ответ для вашего конкретного приложения.

person Nikos Athanasiou    schedule 21.07.2015
comment
Добавление ссылки на тест, который вы написали ранее... Ура. - person Tony Delroy; 24.07.2015

Разница заключается не в заказе или его отсутствии, а в контейнере для поддержки. Если это непрерывная память, она должна быстро перебираться из-за простой реализации итератора и удобства кэширования.

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

Вообще говоря, производительность контейнера — довольно сложная тема, и обычно ее нужно тестировать в реальном приложении, чтобы получить надежный ответ. Существует множество вещей, определяемых реализацией, которые могут повлиять на производительность. Я бы выбрал hash_set, если бы мне пришлось идти вслепую. Копирование в vector также может оказаться хорошим вариантом.

РЕДАКТИРОВАТЬ: Как сказал @TonyD в своем комментарии, существует правило, запрещающее аннулировать итераторы во время добавления элемента, когда max_load_factor() не превышено, это практически исключает резервные контейнеры, которые непрерывны в памяти.

Таким образом, копирование всего в вектор кажется еще более разумным вариантом. Если вам нужно удалить дубликаты, возможным вариантом может быть использование http://en.cppreference.com/w/cpp/algorithm/sort и легкое игнорирование дубликатов. Я слышал, что использование vector и sort для отсортированного массива (или вектора) довольно часто используется в случае необходимости в контейнере, который должен быть сортировщиком и чаще повторяется, чем модифицируется.

person luk32    schedule 21.07.2015
comment
Неупорядоченные контейнеры обычно хранятся как вектор векторов (или что-то подобное), только если вы считаете вектор связанных списков похожим (я так не считаю): не свисающие смежные векторы элементов с сегментов практически гарантировано, учитывая требование стандарта, что существующие объекты не перемещаются во время вставок, которые не увеличивают коэффициент загрузки выше max_load_factor(), тем самым вызывая перехеширование всей таблицы. Не так много осталось для выбора реализации, как думает большинство людей, хотя вы упоминаете hash_set, которое было общим названием для реализаций до С++ 11, и они различались.... - person Tony Delroy; 21.07.2015
comment
@TonyD Я также не считаю их похожими, согласно моему 1-му абзацу, здесь очень важна смежность памяти. Я знаю, что места для перемещения меньше, чем можно себе представить, я думаю, что однажды у меня была такая (кстати, отличная) дискуссия (я даже думаю, что это было с вами), что есть тонкие правила, которые в основном исключают некоторые реализации. Тем не менее, все еще достаточно, чтобы повлиять на производительность в некоторых случаях. ИМО он очень хрупкий и действительно нуждается в измерении. Тем не менее я обновлю ответ. Копирование в вектор может стать лучшим вариантом. - person luk32; 21.07.2015

итерация от самого быстрого к самому медленному должна быть: set > map > unordered_set > unordered_map; set немного легче, чем map, и они упорядочены по правилу двоичного дерева, поэтому должны работать быстрее, чем контейнеры unordered_.

person hero    schedule 21.07.2015