Это медленнее из-за двух поисков вместо одного?

Когда я хочу убедиться, что запись, которую я хочу использовать, существует, я обычно делаю это.

#include <unordered_map>

struct type { int member; };
std::unordered_map<type> map;

if (map.find(key) != map.end())
    map[key].member = 42;

Однако, я думаю, что он выполняет два поиска для key в хэш-карте. Это кэширует поиск.

#include <unordered_map>

struct type { int member; };
std::unordered_map<type> map;

auto find = map.find(key);
if (find != map.end())
    find->second.member = 42;

Первый вариант кажется более выразительным. Это действительно медленнее?

Ответы

Ответ 1

Да, потому что вы дважды просматриваете ключ: map[key] найдите ключ точно так же, как map.find, из которого вы отбросили результат.

Как открыть ящик, чтобы увидеть, есть ли данный объект, скажите "ай да!". и закройте ящик, затем откройте его снова и исследуйте объект, чтобы изменить его.

Второй код открывает ящик, ищет объект и меняет его.

Могут быть оптимизаторы компилятора, которые позволяют избежать двойного поиска или могут сократить поиск в постоянное время, и может быть оптимизация компилятора, которая позволяет избежать сохранения переменной auto find в памяти (это может быть регистр ЦП, поскольку его использование является очень локальным).

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

2*H < H+M

Это означает H < M. Если M является регистром, а H не является тривиальным, то для H трудно быть меньше M.

Ответ 2

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

Ответ 3

Да, это может быть медленнее, но, вероятно, не заметно медленнее. Существует несколько дополнительных работ:

  • Хэш скорее всего будет вычислен дважды, если у вас нет достаточно интеллектуального компилятора, используйте расширения поставщика, такие как pure или const или использовать аналогичный метод. Обратите внимание, что если хеш тривиален, а компилятор знает, что код, большинство компиляторов, вероятно, в настоящее время достаточно умны.
  • Положение ведра необходимо найти во второй раз (если компилятор не заметит, что это тот же хэш, поэтому его не нужно перекомпоновать)
  • Необходимо выполнить обход столкновений (или аналогичный метод в зависимости от разрешения столкновения). Опять же - достаточно умный компилятор может заметить, что мы делаем это дважды, мы фактически ничего не модифицируем и т.д. У нас могут быть такие компиляторы в настоящее время, но я не уверен на 100%, если мы там. Даже если они не являются кэшированными чтениями, и они, вероятно, не будут налагать никаких значительных затрат (по сравнению, например, с использованием хэша или пропущенного чтения). Не вдаваясь в подробности архитектуры процессора L1 $read hit занимает ~ 4 такта латентности на i7 (данные из памяти могут быть неправильными), и процессор может выполнять другую работу во время ожидания.

Итак, подытожим if:

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

тогда вы можете увидеть разницу.


Как последнее слово - это, вероятно, не имеет значения, и это не большая архитектурная разница, а оптимизация 5s. Поэтому напишите все, что вам будет легче поддерживать, и перейдите к вопросу, когда профилировщик покажет, что эти функции приводят к замедлению.

Ответ 4

Если у вас есть определенная причина для сохранения значения в существующей записи (если она уже существует), вы можете полностью пропустить первый поиск и просто установить новое значение:

#include <unordered_map>

struct type { int member; };
std::unordered_map<key_t, type> map;

map[key].member = 42;

Это изменит существующую запись (если она есть) и вставьте новую запись, если она не существует.

Ответ 5

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

auto thing = getThing(key);
if (thing) 
  thing->member = 42;