High-performance, memory-efficient C++ open addressing flat hash table
emhash is a family of high-performance, header-only hash table implementations designed for maximum performance and memory efficiency. Through innovative collision resolution strategies and cache optimization techniques, emhash demonstrates exceptional performance across various benchmarks.
- Core Features
- Quick Start
- Version Selection Guide
- Testing
- Documentation
- Third-Party Benchmarks
- License
- High load factor support: Set load factor up to 0.999 via
EMH_HIGH_LOADmacro (available inhash_table5/7/8) ormax_load_factor()(emhash6/7) - No tombstones: Performance does not degrade even with frequent insert/erase operations
- Smart collision resolution: Three-way hybrid strategy combining linear probing, quadratic probing, and bidirectional search
- Cache-friendly design: Single array inline storage minimizes memory footprint and maximizes cache hit rate
- Compact layout: Significant memory savings when
sizeof(key) % 8 != sizeof(value) % 8- Example:
hash_map<uint64_t, uint32_t>saves 1/3 memory compared tohash_map<uint64_t, uint64_t>
- Example:
- Dynamic shrinking:
shrink_to_fit()releases unused memory
| Feature | Description |
|---|---|
insert_unique |
Direct insertion without lookup (performance boost) |
try_get |
Returns pointer to value, nullptr if key not found |
try_set |
Set value if key exists, do nothing if it doesn't (emhash5/8, emilib1/2/3) |
set_get |
Updates value and returns old value (emhash5/8, emilib1/2/3) |
_erase |
Delete operation returning void (faster) |
| LRU Mode | Enable LRU cache optimization with EMH_LRU_SET |
- Operating Systems: Windows, Linux, macOS
- Processors: x86_64, ARM64 (Apple M1/M2, AMD, Intel)
- Compilers: GCC, Clang, MSVC (C++17/20)
emhash is header-only — just copy one .hpp file to your project:
# Option A: Copy directly (simplest, no build system needed)
cp include/emhash/hash_table7.hpp /your/project/emhash/
# Option B: Clone and include
git clone https://github.com/ktprime/emhash.git
# Then add -I/path/to/emhash/include to your compiler flags
# Option C: CMake FetchContent (no install needed)
# Add to your CMakeLists.txt:
# include(FetchContent)
# FetchContent_Declare(emhash GIT_REPOSITORY https://github.com/ktprime/emhash.git GIT_TAG v1.1.0)
# FetchContent_MakeAvailable(emhash)
# target_link_libraries(your_target PRIVATE emhash::emhash)
# Option D: CMake find_package (after install)
# cmake -B build -DCMAKE_PREFIX_PATH=/path/to/emhash-install
# In CMakeLists.txt: find_package(emhash REQUIRED CONFIG)
# target_link_libraries(your_target PRIVATE emhash::emhash)#include "emhash/hash_table7.hpp" // Or emhash/hash_table[5-8].hpp#include "emhash/hash_table7.hpp"
#include <iostream>
int main() {
// Create and reserve space
emhash7::HashMap<int, std::string> map;
map.reserve(100);
// Insert elements (multiple ways)
map[1] = "one";
map.emplace(2, "two");
map.insert_unique(3, "three"); // Best performance, key must be unique
// Find element
auto it = map.find(2);
if (it != map.end()) {
std::cout << "Found: " << it->second << "\n";
}
// try_get: returns pointer, more concise
if (auto* pval = map.try_get(3)) {
std::cout << "Value: " << *pval << "\n";
}
// Iterate
for (const auto& [key, value] : map) {
std::cout << key << " => " << value << "\n";
}
return 0;
}// Custom key type
struct Point {
int x, y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
struct PointHash {
size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
emhash7::HashMap<Point, std::string, PointHash> point_map;
// C++20 using Lambda
#if __cplusplus >= 202002L
auto hash = [](const Point& p) { return std::hash<int>()(p.x + p.y); };
auto eq = [](const Point& a, const Point& b) { return a.x == b.x && a.y == b.y; };
emhash7::HashMap<Point, int, decltype(hash), decltype(eq)> map(0, hash, eq);
#endifMore examples: docs/examples/
If you're not sure which version to use, start with
emhash7— no tombstones means stable performance under mixed insert/erase workloads, with native high load factor support (0.9+).
Choose by your primary workload pattern, not key type:
What is your primary workload?
├─ Mixed (insert + find + erase) → emhash7 (no tombstones, stable at high LF)
├─ Find/erase-heavy →
│ ├─ Integer keys → emhash6 (bitmask-accelerated empty-slot search)
│ └─ String/large KV types → emhash8 (dense pairs, cache-friendly)
├─ Iteration-heavy → emhash8 (dense pairs array, sequential scan)
└─ Small tables (< 1K elements) → emhash5 (small-size optimization)
Consider emilib2 (Swiss Table) if:
- You need maximum find throughput at scale (1M+ elements) on GCC
- Your workload is read-heavy with minimal erase (tombstone accumulation degrades mixed workloads)
- Note: insert performance is significantly slower on Clang vs GCC
| Version | Best For | Key Strengths | Weaknesses |
|---|---|---|---|
| emhash5 | Small tables, memory-constrained | Small-size optimization (EMH_SMALL_SIZE), 3-way hybrid probing |
Slower than emhash6 at high load factor |
| emhash6 | Find/erase-heavy, integer keys | Bitmask-accelerated empty-slot search, fastest find/erase | Extra memory for bitmask array |
| emhash7 | General purpose, mixed workloads | Chain repair on erase (no tombstones), stable at 0.9+ LF | Erase slightly slower than emhash5/6 |
| emhash8 | Iteration-heavy, large KV types | Split-index + dense pairs, sequential iteration, fast copy/move | Extra memory for separate index array |
| emilib2/3 | Read-heavy at scale (GCC) | SIMD group probing (16 buckets/cycle), excellent iteration | Tombstone accumulation under mixed workloads; insert slower on Clang; emilib2ss may hang under extreme hash collision attack — use emilib2o or emilib2s |
| emilib4 | Experimental Swiss-table variant | Fast insert on Clang, dense iteration | Tombstone accumulation (no backward shift); no try_set/set_get/_erase; fixed LF 0.875 |
| Feature | emhash5 | emhash6 | emhash7 | emhash8 | emilib1/2/3 | emilib4 |
|---|---|---|---|---|---|---|
| High load factor (0.9+) | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
try_set |
✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
set_get |
✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
| Custom allocator | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| SIMD acceleration | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| No tombstones | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Dense pairs iteration | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
See Performance Overview for detailed benchmark numbers.
The test suite is in tests/ and requires no third-party dependencies — only emhash/emilib headers.
cd tests
# Build all tests
cmake -B build && cmake --build build --config Release
# Run via custom targets
cmake --build build --target quick_test # Unit + memory tests (fast feedback)
cmake --build build --target stress_test # Stress tests
cmake --build build --target attack_test # Hash attack tests
cmake --build build --target all_tests # All tests| Category | Directory | Description |
|---|---|---|
| Unit | tests/unit/ |
CRUD, iterators, copy/move, edge cases, full API coverage |
| Memory | tests/memory/ |
ASan/MSan/UBSan, leak detection, lifecycle audit |
| Stress | tests/stress/ |
High load factor, bad hash, randomized stress |
| Attack | tests/attack/ |
Hash collision attacks (constant/small-range/linear) |
| Fuzz | tests/fuzz/ |
LibFuzzer + ASan fuzzing (requires clang) |
See tests/README.md for detailed instructions.
| Document | Description |
|---|---|
| Test Suite | Test organization, build instructions, coverage matrix |
| Test Analysis & Coverage Report | Per-file coverage data, test classification, CI integration |
| Performance Overview | Benchmark results, high load factor performance |
| API Reference | Constructors, methods, iterators |
| Design Principles | Collision resolution, memory layout, implementation comparison |
| Usage Notes | Thread safety, reference stability, iteration, large values |
| Performance Tips | Compile flags, pre-allocation, hash selection, anti-patterns |
| FAQ | Frequently asked questions |
| Migration Guide | Migrating from std::unordered_map |
| Performance Tracking | Version-by-version benchmark history, regression policy |
| Architecture Decisions (ADR) | Why we chose open addressing, header-only, emhash8 layout, etc. |
emhash has been validated by multiple well-known third-party benchmarks:
- Martin Ankerl's Hashmap Benchmark - Comprehensive performance testing
- Jackson Allan's C/C++ Hash Tables Benchmark - Multi-dimensional comparison
Historical performance charts are available in docs/images/:
int64_t*.png- Integer key benchmarksstring*.png- String key benchmarksint_string.png- Mixed key/value type benchmarks
Download all files in the bench/tsl_bench/ directory and open chartsAll.html in a browser to view interactive performance curves.
This project is open-sourced under the MIT License.
Copyright (c) 2019-2026 Huang Yuanbing & bailuzhou AT 163.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
Thanks to the following projects and authors for inspiration and comparison:
- martinus/robin-hood-hashing
- greg7mdp/parallel-hashmap
- Tessil/hopscotch-map
- ska::flat_hash_map
- wyhash — integrated directly into config.hpp (no external dependency required); pass
-DEMH_NO_BUILTIN_WYHASHto use your own wyhash.h