set vs unordered_set для самой быстрой итерации

В моем приложении у меня есть следующие требования:

  1. Структура данных будет заполнена только один раз некоторыми значениями (не парами ключ/значение). Значения могут повторяться, но я хочу, чтобы структура данных сохраняла их только один раз.

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

Ограничение 1 предполагает, что мне придется либо использовать set, либо unordered_set, поскольку данные не представлены в виде пар ключ-значение.

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

Я считаю, что решающим фактором будет то, насколько быстро я смогу перебирать все элементы структуры данных. Я не уверен, будет ли set или unordered_set быстрее для этой цели. Я считаю, что стандарт не упоминает об этом факте, поскольку эта операция будет O (n) для любой структуры данных. Но мне интересно, для какой структуры данных iterator.next() будет быстрее.


person Aviral Goel    schedule 01.07.2014    source источник
comment
Так что измерь и посмотри.   -  person juanchopanza    schedule 01.07.2014
comment
Как насчет вектора?   -  person Kerrek SB    schedule 01.07.2014
comment
Конечно, я могу это сделать, но мне интересно, есть ли у кого-нибудь понимание этого, которое будет общеприменимым независимо от версии GCC.   -  person Aviral Goel    schedule 01.07.2014
comment
Вы можете использовать разные структуры для разных целей. Например, создайте set, а затем скопируйте/переместите все в vector для быстрой итерации. Таким образом, возникает вопрос: нужно ли вам что-нибудь еще, кроме итерации после того, как она заморожена? (например, быстрый поиск)   -  person Matthieu M.    schedule 01.07.2014
comment
@MatthieuM.: Тогда вы можете сразу же создать данные в векторе...   -  person Kerrek SB    schedule 01.07.2014
comment
@KerrekSB Сначала я хочу убедиться, что несколько копий значений данных не входят в структуру данных. В случае тестирования массива это для каждой вставки сделает процесс создания структуры данных дорогостоящим.   -  person Aviral Goel    schedule 01.07.2014
comment
Вставить в вектор, отсортировать и удалить дубликаты?   -  person Some programmer dude    schedule 01.07.2014
comment
@AviralGoel: Вы думаете, набор волшебным образом не должен платить эту цену?   -  person Kerrek SB    schedule 01.07.2014
comment
@JoachimPileborg: std::lower_bound :-S   -  person Kerrek SB    schedule 01.07.2014
comment
@KerrekSB: тогда вы либо рискуете значительно увеличить пиковое использование памяти, если повторы очень распространены, и вы устраняете их после всех вставок, либо замедляете работу до O (N‹sup›2‹/sup›) во время вставки. Конечно, использование памяти на самом деле может быть меньше, если дубликаты встречаются редко, а накладные расходы памяти для дерева преобладают....   -  person Tony Delroy    schedule 01.07.2014
comment
На самом деле этот вопрос не ясен: это зависит от того, 1) определяет ли тип данных строгий порядок, 2) определяет равенство, 3) совместимы ли порядок и равенство. В зависимости от этих условий могут быть доступны не все параметры.   -  person Kerrek SB    schedule 01.07.2014
comment
@KerrekSB: я отредактировал свой комментарий выше, чтобы уточнить это.   -  person Tony Delroy    schedule 01.07.2014
comment
@KerrekSB значения данных являются указателями. Я считаю, что указатели определяют строгий порядок и равенство.   -  person Aviral Goel    schedule 01.07.2014
comment
@AviralGoel: указатели на самом деле совершенно несопоставимы, если только вы не используете std::less<T*>...   -  person Kerrek SB    schedule 01.07.2014
comment
@TonyD: Теоретическая стоимость здесь является огромным отвлекающим маневром, поскольку вы получаете значительные преимущества от локальности памяти вектора. Так что действительно надо мерить. Вставка в середину вектора указателей на самом деле не очень медленная.   -  person Kerrek SB    schedule 01.07.2014
comment
@KerrekSB: я хотел бы отметить, что ОП не беспокоилась о производительности вставки.   -  person Matthieu M.    schedule 01.07.2014
comment
@KerrekSB: верно, для нескольких сотен или тысяч элементов, но вопрос касается эффективности большого O, что подразумевает гораздо, гораздо больше.   -  person Tony Delroy    schedule 01.07.2014
comment
Если заполнить только один раз, отсортированный вектор будет намного быстрее, чем заданный, как при повторении, так и при поиске.   -  person TNA    schedule 01.07.2014
comment
@TNA: Да, это может быть медленнее, чем unordered_set при поиске (в зависимости от количества элементов), потому что для этого требуется загрузить log (N) строк кэша, тогда как хэш-таблица загружает фиксированное количество строк кэша (для заданной нагрузки фактор).   -  person Matthieu M.    schedule 01.07.2014


Ответы (5)


Есть несколько подходов.

  1. В комментариях к вашему вопросу предлагается сохранить std::unordered_set, который имеет самый быстрый O(1) поиск/вставку и O(N) итерацию (как и каждый контейнер). Если у вас есть данные, которые сильно меняются или требуют много случайных поисков, это, вероятно, самый быстрый способ. Но проверить.
  2. Если вам нужно повторить 100 раз без промежуточных вставок, вы можете сделать одну O(N) копию в std::vector и получить выгоду от непрерывной разметки памяти 100 раз. Проверьте, работает ли он быстрее обычного std::unordered_set.
  3. Если у вас есть небольшое количество промежуточных вставок между итерациями, может быть полезно использовать выделенный вектор. Если вы можете использовать Boost.Container, попробуйте boost::flat_set, который предлагает интерфейс std::set с внутренним хранилищем std::vector (т. е. расположение непрерывной памяти, которое очень удобно для кэширования и предварительной выборки). Опять же, проверьте, дает ли это ускорение по сравнению с двумя другими решениями.

Для последнего решения см. документацию Boost для некоторых компромиссов (полезно знать обо всех других проблемах, таких как недействительность итератора, семантика перемещения и безопасность исключений):

Контейнеры Boost.Container flat_[multi]map/set — это ассоциативные контейнеры на основе упорядоченных векторов, основанные на рекомендациях Остерна и Александреску. Эти упорядоченные векторные контейнеры также недавно получили преимущества благодаря добавлению семантики перемещения в C++, что значительно ускорило время вставки и стирания. Плоские ассоциативные контейнеры имеют следующие атрибуты:

  • Более быстрый поиск, чем стандартные ассоциативные контейнеры
  • Гораздо более быстрая итерация, чем стандартные ассоциативные контейнеры.
  • Меньшее потребление памяти для небольших объектов (и для больших объектов, если используется сжатие_в_подгонку)
  • Улучшена производительность кеша (данные хранятся в непрерывной памяти)
  • Нестабильные итераторы (итераторы становятся недействительными при вставке и удалении элементов)
  • Не копируемые и неперемещаемые типы значений не могут быть сохранены
  • Более слабая безопасность исключений, чем у стандартных ассоциативных контейнеров (конструкторы копирования/перемещения могут генерировать ошибки при смещении значений при стирании и вставке)
  • Медленнее вставка и стирание, чем у стандартных ассоциативных контейнеров (особенно для неподвижных типов).

ПРИМЕЧАНИЕ: под более быстрым поиском подразумевается, что flat_set выполняет O(log N) в непрерывной памяти, а не O(log N) гоняется за указателем обычного std::set. Конечно, std::unordered_set выполняет поиск O(1), что будет быстрее для больших N.

person TemplateRex    schedule 01.07.2014
comment
более быстрый поиск, чем хэш-контейнеры? Сомневаюсь. - person Deduplicator; 01.07.2014
comment
@Deduplicator они имеют в виду быстрее, чем std::set, который выполняет O(log N) поиск указателя, а не O(log N) двоичный поиск в непрерывной памяти. - person TemplateRex; 01.07.2014
comment
Ну, тогда вам следует добавить поясняющий комментарий на этот счет. - person Deduplicator; 01.07.2014
comment
кажется немного излишним, когда достаточно простого std::vector. - person MatthiasB; 01.07.2014
comment
@MatthiasB может быть, но это замена std::set, так что вам не нужно менять код вызова. - person TemplateRex; 01.07.2014
comment
Это подходит только для относительно небольших N, в противном случае предложение Матье М. скопировать из a/n [unordered_]set в vector разумно. - person Tony Delroy; 01.07.2014
comment
@TonyD спасибо, обновлено, чтобы отразить различные варианты использования - person TemplateRex; 01.07.2014

Я предлагаю вам использовать set или unordered_set для «фильтрации», а когда вы закончите, переместите данные в вектор фиксированного размера.

person Michał Walenciak    schedule 01.07.2014
comment
unordered_set плохой вариант для фильтрации по сравнению с set. тестирование, если один элемент уже существует, является линейным временем. - person UmNyobe; 01.07.2014
comment
@UmNyobe в среднем unordered_set имеет постоянную сложность поиска по сравнению с логарифмической set - person MatthiasB; 01.07.2014

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

Для ускорения первоначального построения структуры данных вы можете сначала вставить в std::unordered_set или, по крайней мере, использовать его для проверки существования перед вставкой.

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

std::vector<T> v;
auto h = [&v](size_t i){return std::hash<T>()(v[i]);};
auto c = [&v](size_t a, size_t b){return v[a] == v[b];};
std::unordered_set<size_t, decltype(h), decltype(c)> tester(0, h, c);
person Deduplicator    schedule 01.07.2014

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

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

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

В свете вышесказанного я предлагаю вам использовать отсортированный вектор для вашего «набора». Используя деление пополам, вы можете искать в хранилище за время O (log n), а итерация по списку тривиальна. Дополнительным преимуществом вектора является то, что память является непрерывной, поэтому у вас меньше шансов столкнуться с промахами кеша.

person doron    schedule 01.07.2014

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

В этом случае отсортированный вектор может быть хорошим выбором. vector — это динамический массив, поэтому он не требует дополнительных затрат.

Просто непосредственно, см. код.

std::vector<int> data;

int input;
for (int i = 0; i < 10; i++)
{
    std::cin >> input;
    data.push_back(input); // store data
}

std::sort(data.begin(), data.end()); // sort data

Это все. Все ваши данные готовы.

Если вам нужно удалить дубликаты, такие как set, просто используйте unique - erase после сортировки.

data.erase(
    std::unique(data.begin(), data.end()),
    data.end()
    );

Обратите внимание, что вы должны использовать lower_bound, upper_bound и equal_range, а не find или find_if, чтобы использовать преимущества отсортированных данных.

person ikh    schedule 01.07.2014