Hopscotch-Map: A Fast C++ Hash Map Using Hopscotch Hashing
Tessil's hopscotch-map is a header-only C++ library that implements open-addressing hash maps and sets using hopscotch hashing to resolve collisions. It's designed to be cache-friendly and offers better performance than std::unordered_map in most cases, while closely matching google::dense_hash_map but using less memory and providing more functionality.
Key Classes and Variants
The library provides four main classes:
tsl::hopscotch_mapandtsl::hopscotch_set- use a power-of-two growth policy for speed.tsl::hopscotch_pg_mapandtsl::hopscotch_pg_set- use a prime growth policy for better hash distribution.
Additionally, tsl::bhopscotch_map, tsl::bhopscotch_set, tsl::bhopscotch_pg_map, and tsl::bhopscotch_pg_set require the key to be LessThanComparable but guarantee O(log n) worst-case lookups, making them resistant to hash DoS attacks.
Key Features
- Header-only: Just add the
includedirectory to your include path. CMake integration viatsl::hopscotch_maptarget. - Fast: Benchmarks show it outperforms
std::unordered_mapand is competitive withgoogle::dense_hash_map. - Move-only and non-default constructible keys/values: Fully supported.
- Heterogeneous lookups: Allows
find()with a different type thanKey(e.g.,foo*when key isstd::unique_ptr). RequiresKeyEqual::is_transparent. - No sentinel values: No need to reserve special values for empty/deleted keys.
- StoreHash option: Store the hash value on insert to speed up rehash and lookups when hash or key equality is expensive.
- Precalculated hash: Pass a known hash to
find()for faster lookup. - Exception-free support: Compile with
-fno-exceptionsor defineTSL_NO_EXCEPTIONS; usesstd::terminateinstead of throw. - API similar to std::unordered_map: But with some differences.
Differences from std::unordered_map
- Iterator invalidation: Any modifying operation except
eraseinvalidates all iterators. - References/pointers to keys/values are invalidated similarly on insert.
operator*()returnsconst std::pairinstead ofstd::pair. To modify the value, useit.value() = ....- Move-only types must have a nothrow move constructor.
- No bucket-related methods like
bucket_size.
Growth Policies
The GrowthPolicy template parameter controls how the hash table grows. Three policies are provided:
tsl::hh::power_of_two_growth_policy(default): Uses fast modulo via bitmask, but can cause collisions with poor hash functions.tsl::hh::prime_growth_policy(default for_pgvariants): Uses prime numbers for better hash distribution, slightly slower.tsl::hh::mod_growth_policy: Customizable growth factor, uses modulo directly.
If you see non-zero overflow_size(), you likely have many collisions. Consider a better hash function or a prime growth policy.
Installation
Simply add the include directory to your project. With CMake:
add_subdirectory(third-party/hopscotch-map)
target_link_libraries(your_target PRIVATE tsl::hopscotch_map)
Or install via make install and use find_package(tsl-hopscotch-map REQUIRED).
Code Example
#include
#include
#include
int main() {
tsl::hopscotch_map map = {{"a", 1}, {"b", 2}};
map["c"] = 3;
map.insert({"e", 5});
map.erase("b");
for(auto it = map.begin(); it != map.end(); ++it) {
it.value() += 2; // Modify value
}
for(const auto& key_value : map) {
std::cout << "{" << key_value.first << ", " << key_value.second << "}\n";
}
// Heterogeneous lookup example
tsl::hopscotch_map map2;
map2["a"] = 1;
const std::size_t hash = std::hash{}();
if(map2.find("a", hash) != map2.end()) {
std::cout << "Found with precomputed hash\n";
}
// StoreHash example
tsl::hopscotch_map,
std::equal_to,
std::allocator>,
30, true> map3;
map3["a"] = 1;
// bhopscotch_map for DoS resistance
tsl::bhopscotch_map safe_map;
safe_map[1] = 10;
return 0;
}
Heterogeneous Lookups
Activate by defining KeyEqual::is_transparent. Example using std::equal_to<>:
struct employee {
int id;
std::string name;
friend bool operator==(const employee& e, int id) { return e.id == id; }
};
struct hash_employee {
std::size_t operator()(const employee& e) const { return std::hash()(e.id); }
std::size_t operator()(int id) const { return std::hash()(id); }
};
struct equal_employee {
using is_transparent = void;
bool operator()(const employee& e, int id) const { return e.id == id; }
bool operator()(int id, const employee& e) const { return id == e.id; }
bool operator()(const employee& a, const employee& b) const { return a.id == b.id; }
};
int main() {
tsl::hopscotch_map map;
map[employee{1, "Alice"}] = 100;
auto it = map.find(1); // Find by int
std::cout << it->second; // 100
}
Performance Considerations
- Use
tsl::hopscotch_mapas default. - If hash quality is a concern, use
tsl::hopscotch_pg_maportsl::bhopscotch_map. - For DoS resistance, use
tsl::bhopscotch_mapwhich guarantees O(log n) worst-case lookups. - Enable
StoreHashif hash computation is expensive.
Conclusion
Hopscotch-map is a solid choice for C++ projects needing a fast, memory-efficient hash map. Its header-only nature and close API to std::unordered_map make it easy to adopt. For security-critical contexts, the bhopscotch variants provide a strong guarantee against collision attacks. Check the benchmark page for numbers comparing against std::unordered_map, google::dense_hash_map, and others.
