Карта C++ STL против векторной скорости

В интерпретаторе для моего экспериментального языка программирования у меня есть таблица символов. Каждый символ состоит из имени и значения (значение может быть, например, типа string, int, function и т. д.).

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

Тогда я решил использовать карту, в моем случае map<string,symbol> это было бы лучше, чем постоянно повторять вектор, но:

Немного сложно объяснить эту часть, но я попытаюсь.

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

Так что я мог бы использовать карту для получения переменной: SymbolTable[ myVar.Name ]

Но подумайте о следующем: если переменная, все еще использующая вектор, найдена в первый раз, я могу сохранить ее точную целочисленную позицию в векторе вместе с ней. Это означает: в следующий раз, когда это понадобится, мой интерпретатор знает, что он был «кэширован», и не ищет его в таблице символов, а делает что-то вроде SymbolTable.at( myVar.CachedPosition ).

Теперь мой (довольно сложный?) вопрос:

  • Должен ли я использовать вектор для таблицы символов вместе с кэшированием положения переменной в векторе?

  • Должен ли я использовать карту? Почему? Насколько быстро работает оператор []?

  • Должен ли я использовать что-то совершенно другое?


person sub    schedule 03.04.2010    source источник
comment
Просто немного пищи для размышлений: если вы сохраняете целочисленную позицию, в которой находится ваша переменная, и какую-то переменную до того, как она будет собрана или удалена мусором, каков ваш план для этого кэша целочисленной позиции?   -  person Blindy    schedule 04.04.2010


Ответы (12)


Фактически у вас есть несколько альтернатив.

Существуют библиотеки:

  • Loki::AssocVector: интерфейс карты, реализованный на основе vector пар, быстрее, чем карта для небольших или замороженных наборов из-за локальности кеша.
  • Boost.MultiIndex: предоставляет как Список с быстрым поиском и пример внедрение списка MRU (последние Используется), который кэширует последние использованные элементы.

Критики

  • Поиск и извлечение карты занимает O(log N), но элементы могут быть разбросаны по всей памяти, что плохо сочетается со стратегиями кэширования.
  • Вектор более удобен для кеша, однако, если вы не отсортируете его, у вас будет O(N) производительность на find, приемлемо ли это?
  • Почему бы не использовать unordered_map ? Они обеспечивают O(1) поиск и извлечение (хотя константа может быть высокой) и, безусловно, подходят для этой задачи. Если вы посмотрите статью Википедии о хеш-таблицах, вы поймете, что существует множество доступных стратегий. и вы, безусловно, можете выбрать тот, который будет соответствовать вашему конкретному шаблону использования.
person Matthieu M.    schedule 04.04.2010
comment
Если вы держите вектор отсортированным (штраф за вставку), то find будет иметь производительность O(log N). - person Randy the Dev; 25.07.2017
comment
Мой вектор отсортирован, как добиться O(log N) производительности? - person John; 13.07.2021
comment
@John: Вам нужно использовать алгоритм бинарного поиска. В C++ этого можно добиться с помощью алгоритмов std::lower_bound и std::upper_bound (работает любой из них). - person Matthieu M.; 13.07.2021
comment
@Matthieu M. Кажется, что std::vector::lower_bound медленнее, чем std::map::find, даже если набор данных небольшой (300 ~ 5000). Это действительно неожиданно. Я делал такие тесты на Ubuntu два часа назад. Что вы об этом думаете? - person John; 13.07.2021
comment
@John: Я думаю, вам нужно задать правильный вопрос и показать свой код, компилятор и настройки компилятора и т. д. Это действительно неожиданно. - person Matthieu M.; 13.07.2021

Карта — это хорошая вещь для таблицы символов. но operator[] для карт нет. В общем, если вы не пишете тривиальный код, вы должны использовать функции-члены карты insert() и find() вместо operator[]. Семантика operator[] несколько сложна и почти наверняка не будет делать то, что вам нужно, если искомого символа нет на карте.

Что касается выбора между map и unordered_map, разница в производительности вряд ли будет существенной при реализации простого языка интерпретации. Если вы используете карту, вам гарантировано, что она будет поддерживаться всеми текущими реализациями Standard C++.

person Community    schedule 03.04.2010
comment
Чтобы «кэшировать» символ, просто сохраните итератор, возвращенный std::map::find(), вместо позиции этого символа из std::vector. - person moatPylon; 04.04.2010

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

person Mike Dinsdale    schedule 03.04.2010
comment
Вы не знаете мой код, у меня есть очень хороший вариант для сохранения позиции, поверьте мне - person sub; 04.04.2010
comment
Затем сохраните указатель (например, символ *) и полностью обойдите поиск контейнера. - person Ben Voigt; 04.04.2010
comment
Вы, конечно, можете сохранить позицию в своем дереве синтаксического анализа или в каком-либо другом промежуточном представлении, но вы можете сделать то же самое, если используете map, как говорит Троник в своем ответе. Большая разница только во времени, чтобы найти имя переменной. (На самом деле возникают другие проблемы, если вы хотите сериализовать свои структуры данных, но я не буду здесь размышлять о вашем коде;)) - person Mike Dinsdale; 04.04.2010

Оператор std::map [] занимает время O (log (n)). Это означает, что он достаточно эффективен, но вам все же следует избегать повторного поиска. Возможно, вместо хранения индекса вы можете сохранить ссылку на значение или итератор на контейнер? Это позволяет полностью избежать поиска.

person Tronic    schedule 03.04.2010

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

Например, Python (реализация C) изменяет локальные переменные в ссылки по индексу, но на глобальные переменные и переменные класса ссылаются по имени, используя хеш-таблицу.

Предлагаю посмотреть вводный текст по компиляторам.

person Dietrich Epp    schedule 03.04.2010

std::map (O(log(n))) или хеш-таблица ("амортизированная" O(1)) будет первым выбором - используйте пользовательские механизмы, если вы определили, что это узкое место. Как правило, использование хэша или токенизация ввода — это первая оптимизация.

Перед профилированием очень важно изолировать поиск, чтобы его можно было легко заменить и профилировать.


std::map, вероятно, немного медленнее для небольшого количества элементов (но тогда это не имеет большого значения).

person peterchen    schedule 03.04.2010

Карта - это O (log N), поэтому не так быстро, как позиционный поиск в массиве. Но точные результаты будут зависеть от множества факторов, поэтому лучший подход — взаимодействовать с контейнером таким образом, чтобы впоследствии можно было переключаться между реализациями. То есть напишите функцию «поиска», которую можно эффективно реализовать любым подходящим контейнером, чтобы позволить себе переключаться и сравнивать скорости разных реализаций.

person Daniel Earwicker    schedule 03.04.2010

Оператор карты [] — это O(log(n)), см. википедию: http://en.wikipedia.org/wiki/Map_(C%2B%2B)

Я думаю, поскольку вы часто ищете символы, использование карты, безусловно, правильно. Возможно, хэш-карта (std::unordered_map) поможет вам производительность лучше.

person Klaim    schedule 03.04.2010

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

Воспользуйтесь советом Нила; map обычно является хорошей структурой данных для таблицы символов, но вам нужно убедиться, что вы используете ее правильно (и не добавляете символы случайно).

person Michael Burr    schedule 03.04.2010

Вы говорите: «Если переменная, все еще использующая вектор, найдена в первый раз, я могу сохранить с ней точную целочисленную позицию в векторе».

Вы можете сделать то же самое с картой: найти переменную, используя find, и сохранить iterator, указывающий на нее, вместо позиции.

person baol    schedule 03.04.2010

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

Реализации карт STL обычно реализуются с помощью самобалансирующихся деревьев, таких как красное черное дерево, и их операции занимают O(logn) времени.

Мой совет заключается в том, чтобы обернуть код манипулирования таблицами в функции,
такие как table_has(name), table_put(name) и table_get(name).

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

person Nick Dandoulakis    schedule 03.04.2010

Карта будет масштабироваться намного лучше, что будет важной особенностью. Однако не забывайте, что при использовании карты вы можете (в отличие от вектора) брать указатели и ссылки. В этом случае вы можете легко «кэшировать» переменные с помощью карты так же точно, как и вектор. Карта почти наверняка является правильным выбором здесь.

person Puppy    schedule 03.04.2010