I have some keys that are pair<string, string>. I was originally going to write my own hash function but thought that it might be easier to just implement an unordered_map<string, unordered_map<string, val>>. Are there any performance differences between these two I should be aware of?
2 Answers
I would use std::unordered_map<std::pair<std::string, std::string>, Value, [pair_hash][1]> for two reasons:
- Performance
Of course, you can measure your two versions with your favorite profiler, but basing on my experience - the number of allocation is what matters here the most - so see:
flat_map.insert(key, value)); will create on average just one new bucket (or extend one), whilst
auto it = map2.insert(make_pair(key.first, map1{}));
it->second.insert(make_pair(key.second, value));
have to create empty map1 - what might not be zero-cost. Then it has to add/extend two buckets (list associated with the given hash value).
- Maintainability/Readability
The Second reason is more important for me. Flat(one) map is easy to use. You could see in insert example already that it is more complicated, but consider erase - it is so complicated, that it is easy to make a mistake:
void remove(
std::unordered_map<std::string,
std::unordered_map<std::string, Value>>& map,
std::pair<std::string, std::string> const& key)
{
auto it1 = map.find(key.first);
if (it1 == map.end()) return;
it1->second.erase(key.second);
// easy to forget part
if (it1->second.empty())
{
map.erase(it1);
}
}
Comments
Defining a simple hash function in your case is trivial and performant. If the std::pair is semantically the key, then this approach makes your intent clear. It also allows duplicates of the first member of the std::pair in your map, as you only need the entire key to be unique. In terms of usage, you also avoid the additional layer of indirection, with nested maps.
Example implementation:
...
using pairSS = std::pair<std::string, std::string>;
namespace std
{
template<> struct hash<pairSS>
{
std::size_t operator()(pairSS const& pair) const noexcept
{
return std::hash<std::string>{}(pair.first) ^
(std::hash<std::string>{}(pair.second) << 1);
}
};
}
int main()
{
std::pair myPair = {"Hi", "bye"};
std::cout << std::hash<pairSS>{}(myPair) << std::endl;
struct val{};
std::unordered_map<pairSS, val> hashMap;
}
std::pair<T,T>type. Qt already has it: source code. Andboost::hash_combinemight be interesting.