Контейнеры STL и большие объемы данных

У меня есть большая коллекция данных, которые считываются в память — временно, но необходимо для системы.

Я проверял производительность std::vector, а также std::unordered_map.
Для std::vector я использовал struct типа:

struct information{
    std::string name;
    unsigned int offset;
}

Для std::unordered_map я использовал std::string для ключа и unsigned int offset для значения.

Если, скажем, 2 000 000 из них загружены в память, я попробовал следующее и получил такие результаты:

std::vector:

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

std::vector<information> vec;
vec.reserve(2500000);

Вставка

vec.push_back({dataName, offset});

довольно быстро. Однако поиск данных происходит очень медленно. Находка была реализована так:

auto it = std::find_if(vec.begin(), vec.end(), [&name](information &info) -> bool {return info.name == name); });

Это имеет смысл, учитывая, что это большой вектор, и правильный struct найден при сравнении имен. Но это была крайне плохая производительность. Используемая память была в порядке - я предполагаю, что часть роста памяти была связана с изменением размера std::string.

Мой вопрос о векторной реализации: есть ли способ увеличить время поиска? Я знаю, что вектор можно отсортировать, чтобы увеличить время поиска, но тогда вы потеряете время на сортировку вектора. Особенно на векторе такого размера.

std::unordered_map:

Вставка

std::unordered_map<std::string, unsigned int> unordMap;
unordMap.reserve(2500000);
unordMap.emplace(name, offset);

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

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

Конечно, поиск очень быстрый. Мой вопрос о std::unordered_map: можно ли улучшить время вставки и использование памяти?

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


person Jan Swart    schedule 28.07.2014    source источник
comment
Время, которое вы потеряли при сортировке вектора, - это только один раз, если вы много ищете, сортировка не будет заметной. Позже вы можете использовать двоичный поиск для поиска.   -  person NetVipeC    schedule 28.07.2014
comment
Я полагаю, вы опечатались здесь push_back()   -  person nasser-sh    schedule 28.07.2014
comment
Кроме того, хотя это может подразумеваться из того факта, что вы выбрали unordered_map, я на всякий случай спрошу: могут ли у вас быть повторяющиеся строки?   -  person nasser-sh    schedule 28.07.2014
comment
Есть ли способ увеличить время поиска... вы имеете в виду скорость поиска?   -  person T.C.    schedule 28.07.2014
comment
Вы используете Visual Studio?   -  person Neil Kirk    schedule 28.07.2014
comment
Лучшее зависит от того, что вы с ним делаете. john-ahlgren.blogspot.com/2013/10/ (в Интернете также есть миллион других копий).   -  person IdeaHat    schedule 28.07.2014
comment
Проблема с этим вопросом заключается в том, что вы задаете в нем около 5 вопросов...   -  person Yakk - Adam Nevraumont    schedule 28.07.2014
comment
По сути, (имхо) оптимизация на 99% состоит в выборе правильных структур данных. Итак, что вы хотите делать со своими данными? какая часть должна быть быстрой? Какую часть можно немного подождать?   -  person IdeaHat    schedule 28.07.2014
comment
Это зависит от того, как вы используете данные и почему вы хотите использовать карту. Одна потенциальная возможность получить лучшее из двух — использовать вектор для хранения и использовать карту в качестве кеша. Строка карты, int*› будет хранить пару «строка-указатель» последнего запроса. Это будет работать нормально, если ваши запросы имеют сильную временную локальность. Для таких больших данных вы не ожидаете, что все данные будут запрашиваться равномерно, и нет смысла сортировать их все в начале.   -  person Hsi-Hung Shih    schedule 28.07.2014
comment
Да, я задаю пару вопросов в одном. Я хотел бы знать, какую структуру данных лучше всего использовать. Для меня это сводилось к двум: std::vector или std::unordered_map. И да, я сейчас использую Visual Studio.   -  person Jan Swart    schedule 28.07.2014
comment
Вы спрашиваете, есть ли способ увеличить время поиска для std::vector. Вы уверены, что не имеете в виду уменьшение?   -  person Adrian    schedule 22.01.2015


Ответы (4)


struct information{
  std::string name;
  unsigned int offset;
  information(information const&)=default;
  information(information&&)=default;
  information(std::string n, unsigned o):name(std::move(n)),offset(o),hash(std::hash<std::string>()(name)) {};
  information():information("",0) {};
  bool operator<( information const& o ) const {
    return tie() < o.tie();
  }
  std::tuple<std::size_t, std::string const&> tie() const { return std::tie(hash, name); }
private:
  std::size_t hash;
};

Используйте приведенную выше структуру для вашего std::vector.

После добавления всех данных std::sort его.

Чтобы найти что-то, совпадающее с name, сделайте следующее:

struct information_searcher {
  struct helper {
    std::tuple<std::size_t, std::string const&> data;
    helper( std::string const& o ):data(std::hash<std::string>()(o), o) {};
    helper( helper const& o ) = default;
    helper( information const& o ):data(o.tie()) {}
    bool operator<( helper const& o ) const { return data < o.data; }
  };
  bool operator()( helper lhs, helper rhs ) const { return lhs < rhs; }
};

information* get_info_by_name( std::string const& name ) {
  auto range = std::equal_range( vec.begin(), vec.end(), information_searcher::helper(name), information_searcher{} );
  if (range.first == range.second) {
    return nullptr;
  } else {
    return &*range.first;
  }
}

который является поиском с почти нулевыми накладными расходами.

Что мы делаем здесь, так это хешируем строки (для быстрого сравнения), возвращаясь к std::string сравнению, если у нас есть коллизия.

information_searcher — это класс, который позволяет нам искать данные без необходимости создавать information (что потребовало бы расточительного выделения памяти).

get_info_by_name возвращает указатель -- nullptr, если он не найден, и указатель на первый элемент с таким именем в противном случае.

Изменение information.name невежливо и делает поле hash неверным.

Это может использовать немного больше памяти, чем наивная версия std::vector.

В общем, если ваша работа заключается в том, чтобы «добавить кучу данных в таблицу», а затем «выполнить множество операций поиска», лучше всего создать std::vector, быстро отсортировать ее, а затем использовать equal_range для выполнения поиска. . map и unordered_map оптимизированы для большого количества смешанных вставок/удалений/и т.д.

person Yakk - Adam Nevraumont    schedule 28.07.2014
comment
Это действительно отличный ответ! Большое спасибо. - person Jan Swart; 29.07.2014
comment
Просто примечание о конструкторе перемещения по умолчанию. VS2013 не создает конструктор перемещения по умолчанию. Неверный тип для конструктора по умолчанию - person Jan Swart; 29.07.2014

vector обычно реализуется как «динамический массив» и должен быть наиболее эффективным с точки зрения памяти. с хорошей стратегией резервирования он может иметь вставку O (1) = fast. Поиск O(n) = очень плохой.

Вы можете помочь вектору, отсортировав его (и если вы сначала загрузите, а затем выполните поиск, чем, я думаю, будет лучше всего - std::sort + std::binary_search).
Вы также можете реализовать что-то вроде сортировки вставкой, используя std: :нижняя граница. вставка = O(log n) = хорошо, поиск = O(log n) = хорошо

карта (упорядоченная) на самом деле может выполнять ту же работу, но также может быть реализована с использованием дерева = менее эффективное использование памяти, доступ так же хорош, как отсортированный вектор (но, возможно, меньше перераспределения, но в вашем случае отсортированный вектор по-прежнему лучше)

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

person firda    schedule 28.07.2014

Проблема с большим вектором заключается во времени поиска, когда вы не знаете индекс нужных вам объектов. Как вы сказали, один из способов улучшить его - сохранить упорядоченный вектор и выполнить по нему двоичный поиск. Таким образом, время поиска будет иметь не линейную, а логарифмическую сложность, что позволяет сэкономить довольно много времени при работе с очень большими контейнерами. Это поиск, используемый в std::map (упорядоченный). Вы можете выполнить аналогичный двоичный поиск, используя std::lower_bound или std::equal_range на вашем std::vector.

Проблема с большой неупорядоченной картой совершенно другая: такой контейнер использует хеш-функцию и вычисление модуля для размещения элементов в соответствии с их ключами в стандартном массиве. Поэтому, когда у вас есть n элементов в std::unordered_map, очень маловероятно, что вам нужен только массив из n элементов, потому что некоторые индексы не будут заполнены. Вы будете использовать по крайней мере наибольший индекс, полученный с помощью хэш-и-модуля. Один из способов улучшить использование памяти, а также время вставки — написать собственную хеш-функцию. Но это может быть сложно в зависимости от того, какие строки вы используете.

person Nelfeal    schedule 28.07.2014
comment
std::hash выдает значения во всем диапазоне std::size_t, а std::unordered_map не имеет такого количества элементов в своем массиве сегментов... - person Yakk - Adam Nevraumont; 28.07.2014
comment
@Yakk: Спасибо, исправлено. Однако я не вижу, как ваше решение делает поиск быстрее, чем напрямую с использованием lower_bound или equal_range. Можете ли вы объяснить (здесь, в своем ответе или в сообщении)? - person Nelfeal; 28.07.2014
comment
Почему я хэшировал? Что ж, сравнение двух std::string происходит медленно (вам нужно получить доступ к памяти, которая не находится рядом (попадание), а затем вам нужно пройтись по ней, пока они не расходятся и/или не достигнут конца (непредсказуемые ответвления)). Сравнение двух std::size_t в массиве происходит быстро (когерентность кеша!). Вы по-прежнему будете сталкиваться с ошибкой прогнозирования ветвления (при условии, что данные находятся в случайной точке контейнера, что вполне вероятно), но их ограниченное количество. Или ты про information_searcher? Это значит, что нам не нужно копировать std::string в information. - person Yakk - Adam Nevraumont; 29.07.2014
comment
@Yakk: О, теперь я вижу, как это работает. Сравнивать size_t действительно быстрее, чем string. Это довольно много специализировано для этого конкретного использования, но я все же кое-чему научился. И последнее, что касается вашего information_searcher: нельзя ли его заменить таким обращением к tie? std::tie(std::hash(name), name) - person Nelfeal; 29.07.2014
comment
попробуй -- нужен либо cast-to-tie для информации, либо 4 перегрузки на компраторе. - person Yakk - Adam Nevraumont; 29.07.2014

Что ж, оптимальным решением здесь будет создание std::map, логарифмической сложности как при вставке, так и при поиске. Хотя я не вижу причин, по которым вы бы не использовали std::vector. Это довольно быстро при использовании быстрой сортировки для сортировки даже 2M элементов, особенно если вы делаете это один раз. std::binary_search работает очень быстро. Подумайте об этом, если вам нужно сделать много поисков между вставками.

person Garrappachc    schedule 28.07.2014
comment
Каким образом std::map будет быстрее, чем std::unordered_map в приведенных выше случаях? - person Yakk - Adam Nevraumont; 28.07.2014
comment
Но карта требует много памяти. Гораздо больше, чем вектор. Я посмотрю на сортировку вектора. - person Jan Swart; 28.07.2014