Более эффективная структура как unordered_map<pair <int, int>, int>
У меня около 20 000 000 pair<int, int>
который мне нужно связать с int
s. Я сделал это с unordered_map<pair<int, int>, int>
, Профилирование моего алгоритма показывает, что проверка, существует ли запись или нет
bool exists = myMap[make_pair(a, b)] != NULL
является узким местом производительности. Я думал, что получение этой информации из unordered_map
будет очень быстро, так как это O (1). Но постоянное время может быть медленным, если постоянная большая...
Моя хеш-функция
template <>
struct tr1::hash<pair<int, int> > {
public:
size_t operator()(pair<int, int> x) const throw() {
size_t h = x.first * 1 + x.second * 100000;
return h;
}
};
Вы знаете лучшую структуру данных для моей проблемы?
Очевидно, что я не могу просто хранить информацию в матрице, поэтому объем памяти не поместится ни в один из существующих компьютеров. Все, что я знаю о распределении, это то, что myMap[make_pair(a, a)]
не существует для любого a
, И это все int
s находятся в непрерывном диапазоне от 0 до около 20000000.
Думайте об этом как о разреженной матрице размером 20 000 000x20 000 000, содержащей около 20 000 000 записей, но не на главной диагонали.
Предложение
Был бы vector<pair<int, int>>*
(массив с N записями) ожидается быстрее? Поиск для a
будет тривиальным (просто индекс массива), а затем я бы перебрать вектор, сравнивая first
значение пары в b
,
БОЛЬШОЕ ОБНОВЛЕНИЕ
Я загрузил необработанные данные, чтобы вы могли видеть структуру.
5 ответов
Как предположить, я пошел с vector<pair<int, int>>*
с N записей. Это примерно на 40% быстрее, чем unordered_map
,
Вы пытались использовать myMap.find(make_pair(a,b)) != myMap.end()
? operator[]
создает элемент, если он не существует. Я бы ожидал find
быть быстрее.
Прежде всего, myMap[make_pair(a, b)] != NULL
не делает то, что вы думаете, что делает. Он вставляет пару, если она не существует, и сравнивает сопоставленное значение с 0 (что NULL
расширяется до). Он вообще не проверяет существование. (Обратите внимание, что в современном C++ вы никогда не должны использовать NULL
, Используйте 0 для чисел и nullptr
для указателей).
Что касается основной темы, ваша хеш-функция не кажется слишком хорошей. Не забывайте, что арифметика на int
с сделано в int
s. Так как на большинстве компиляторов int
является 32-разрядным, его максимальное значение составляет чуть более 2000 000 000. Таким образом, 20 000 000 * 10 000 намного больше, что приводит к переполнению (и неопределенному поведению).
Учитывая количество ваших данных, я предполагаю, что вы на 64-битной платформе, что означает size_t
длиной 64 бита. Таким образом, вы можете получить лучшие результаты с помощью хэш-функции, например:
size_t operator()(pair<int, int> x) const throw() {
size_t f = x.first, s = x.second;
return f << (CHAR_BIT * sizeof(size_t) / 2) | s;
}
Это должно привести к значительно меньшему количеству столкновений (и определенному поведению) по сравнению с тем, что вы имеете сейчас.
Если это не поможет, вы также можете попробовать двухэтапный подход:
std::unordered_map<int, std::unordered_map<int, int>>
Поиск по x.first
сначала, потом x.second
, Я не знаю, поможет ли это; измерить и увидеть.
Главное, безусловно, избегать добавления элементов по умолчанию при каждом поиске:
bool exists = myMap[make_pair(a, b)] != NULL; // OUCH
bool exists = myMap.find(make_pair(a, b)) != myMap.end(); // BETTER
iterator i = myMap.find(make_pair(a, b);
if (i != myMap.end()) ... else ...; // MAY BE BEST - SEE BELOW
И большой вызов хэш... вау-ху! Это может стоить того, но многое зависит от того, как распределены числа в парах и от реализации. std::hash
(что часто бывает сквозным!)
size_t operator()(pair<int, int> x) const throw() {
size_t hf = std::hash(x.first);
return (hf << 2) ^ (hf >> 2) ^ std::hash(x.second);
}
Вы также можете найти его быстрее, если заменить пару на int64_t
s, так что ключевые сравнения - это, безусловно, простые целочисленные сравнения, а не каскадные.
Кроме того, что ты делаешь после теста на существование? Если вам нужно получить доступ / изменить значение, связанное с тем же ключом, вам следует сохранить итератор find
возвращается и избегайте другого поиска.
Я предлагаю вам протестировать с лучшей хэш-функцией. Вы можете найти примеры, если будете искать здесь на SO, но это одна из возможных реализаций.
struct pair_hash {
template <typename T1, typename T2>
size_t operator()(const std::pair<T1, T2> &pr) const {
using std::hash;
return hash<T1>()(pr.first) ^ hash<T2>()(pr.second);
}
};