# emhash
**Repository Path**: eipoz-opensource/emhash
## Basic Information
- **Project Name**: emhash
- **Description**: https://github.com/ktprime/emhash.git
- **Primary Language**: C++
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-05-15
- **Last Updated**: 2025-05-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# emhash - Fast and memory efficient _open addressing C++ flat hash table/map_
> [!NOTE]
> Some features are not enabled by default; they can be enabled through defining macros. Some features may conflict with each other or are difficult to distribute in one header file, so they are distributed in a different file. Not all features are available in one file.
_Third-party bechmarks from and ._
- **Load factor** can be set to `0.999` by setting `EMHASH_HIGH_LOAD = ` (in `hash_table[5-8].hpp`).
- **Header only** support with C++11/14/17/20
- Interface is highly compatible with `std::unordered_map`, with some new functions added for performance.
- `_erase`: return void after erasing
- `shrink_to_fit`: shrink to fit data for memory saving
- `insert_unique`: insert unique key without finding
- `try_find`: return value
- `set_get`: find/insert combined
- **More efficient** than many hash map implementations when key/values are not aligned.
- When `sizeof(key) % 8 != sizeof(value) % 8`
- e.g., `hash_map` can save 1/3 memory compared to `hash_map`
- **LRU mode**: when `EMHASH_LRU_SET` is set, some keys will be marked as frequently accessed; if they are not in the main bucket slot, it will be swapped into it and probed once.
- **No tombstones**: performance will not deteriorate even with frequent insert/erase operations.
- **Fine-tuned implementations**: 4 different implementations, each with a different focus.
- Some are focused on hot value optimization, some on cold (miss) optimization, some on insert/erase performance, etc.
- **Fully tested** on Windows, Linux, and Mac on AMD, Intel, and ARM64 processors, using MSVC, clang, and gcc.
- **Extensive optimizations for integer keys**
## Design
- **Single array & inline entries**: Each node/entry consists of a struct:
```cpp
struct { Key key, size_t bucket, Value value };
```
This design minimizes separate memory footprint.
- **Main bucket mapping**: The primary bucket is always assigned to `key_hash(key) % size` and cannot be occupied (unlike cuckoo hashing). Many operations begin their search from this bucket.
- **Smart collision resolution**: Collisions are resolved using a linked bucket approach, similar to separate chaining. This prevents severe performance degradation due to primary and secondary clustering.
- **Three-way combined probing strategy** for searching empty slots:
- **Linear probing** scans 2-3 CPU cache lines.
- **Quadratic probing** kicks in after limited linear probing.
- **Bidirectional linear search** begins at both ends using the last found empty slot.
- **Optimized linear probing** (in `hash_table5.hpp`):
Traditional linear probing becomes inefficient at high load factors. The new 3-way linear probing strategy significantly improves search performance. Even with a **load factor > 0.9**, benchmarks show it is **2-3 times faster** than conventional search strategies.
- **Secondary/backup hashing function**:
If the input hash has a high collision rate, setting the `EMHASH_SAFE_HASH` macro enables a backup hashing function, defending against hash attacks (with a **10% performance trade-off**).
- **Collision statistics dump**:
Analyze cache performance by dumping collision data, showing the number of probes required for successful/unsuccessful lookups.
- **Efficient batch lookup**:
Uses x86 bit scan instructions (`ctz`) to scan **64 slots** at once.
- **Customizable hash functions**:
Choose different hash algorithms by defining `EMHASH_FIBONACCI_HASH` or `EMHASH_IDENTITY_HASH` based on the use case.
- **Optimized string hashing**:
Uses the third-party [wyhash](https://github.com/wangyi-fudan/wyhash) algorithm for string keys, which is significantly faster than `std::hash`.
## Example Usage of emhash
### Basic Usage
```cpp
// Constructor
emhash5::HashMap m1(4);
m1.reserve(100);
for (int i = 1; i < 100; i++)
m1.emplace_unique(i, i); // Key must be unique; better performance than emplace() or operator[].
auto no_value = m1.at(0); // Returns 0 if key is not found (no exception thrown).
```
### List Initialization
```cpp
emhash5::HashMap m2 = {
{1, "foo"},
{3, "bar"},
{2, "baz"},
};
// Try-get method (returns nullptr if the key does not exist)
auto* pvalue = m2.try_get(1);
// Try-set method (sets the key only if it does not exist)
if (m2.try_set(4, "for")) printf("Set success\n");
if (!m2.try_set(1, "new")) printf("Set failed\n");
// set_get method (returns old value while updating to new)
std::string ovalue = m2.set_get(1, "new"); // ovalue = "foo", now m2[1] = "new"
// Iterating through the map
for (auto& p : m2)
std::cout << " " << p.first << " => " << p.second << '\n';
```
### Copy and Move Constructors
```cpp
// Copy constructor
emhash5::HashMap m3 = m2;
// Move constructor
emhash5::HashMap m4 = std::move(m2);
```
### Insertion Methods
```cpp
m2.insert_unique(4, "four"); // Insert only if key doesn't exist
m2[4] = "four_again"; // Overwrites existing value
m2.emplace(std::make_pair(4, "four"));
m2.insert({{6, "six"}, {5, "five"}});
```
### Range Constructor
```cpp
std::vector, int>> v = { {0x12, 1}, {0x01,-1} };
emhash5::HashMap, double> m5(v.begin(), v.end());
```
### Custom Key Type (Option 1: Using Hash and Equality Comparators)
```cpp
// Define KeyHash and KeyEqual structs and use them in the template
emhash8::HashMap m6 = {
{ {"John", "Doe"}, "example"},
{ {"Mary", "Sue"}, "another"}
};
```
### Custom Key Type (Option 2: Specializing std::hash)
```cpp
// Define a const == operator in your class/struct and specialize std::hash
emhash7::HashMap m7 = {
{ Foo(1), "One"}, { Foo(2), "Two"}, { Foo(3), "Three"}
};
```
### C++20 Support (Using Lambda for Hashing and Comparison)
```cpp
#if CXX20
struct Goo { int val; };
auto hash = [](const Goo &g) { return std::hash{}(g.val); };
auto comp = [](const Goo &l, const Goo &r) { return l.val == r.val; };
emhash5::HashMap m8;
#endif
```
### Efficient Iteration
```cpp
emhash8::HashMap m8 = {{1, 'a'}, {2, 'b'}};
emhash7::HashMap m7 = {{1, 'a'}, {2, 'b'}};
// Structured binding for iteration
for (const auto [k, v] : m8)
printf("%d %c\n", k, v);
for (const auto [value, _, key] : m7)
printf("%d %c\n", key, value); //different from the other hash map
// Accessing values in insertion order
const auto* data = m8.values();
for (int i = 0; i < m8.size(); i++)
printf("%d %c\n", data[i].first, data[i].second);
```
## _Exceptional_ Performance
The benchmark results demonstrate that emhash delivers exceptional performance, even when performing insert and erase operations at an extremely high load factor of 0.999.
```cpp
static void RunHighLoadFactor()
{
std::random_device rd;
const auto rand_key = rd();
#if 1
WyRand srngi(rand_key), srnge(rand_key);
#else
std::mt19937_64 srngi(rand_key), srnge(rand_key);
#endif
const auto max_lf = 0.999f; //<= 0.9999f
const auto vsize = 1u << (20 + rand_key % 6);//must be power of 2
emhash7::HashMap myhash(vsize, max_lf);
auto nowus = getus();
for (size_t i = 0; i < size_t(vsize * max_lf); i++)
myhash.emplace(srngi(), i);
assert(myhash.bucket_count() == vsize); //no rehash
const auto insert_time = getus() - nowus; nowus = getus();
//erase & insert at a fixed load factor
for (size_t i = 0; i < vsize; i++) {
myhash.erase(srnge()); //erase an old key
myhash[srngi()] = 1; //insert a new key
}
const auto erase_time = getus() - nowus;
printf("vsize = %d, load factor = %.4f, insert/erase = %ld/%ld ms\n",
vsize, myhash.load_factor(), insert_time / 1000, erase_time / 1000);
assert(myhash.load_factor() >= max_lf - 0.001);
}
```
```
vsize = 1048576, load factor = 0.9990, insert/erase = 25/76 ms
vsize = 2097152, load factor = 0.9990, insert/erase = 52/222 ms
vsize = 4194304, load factor = 0.9990, insert/erase = 117/450 ms
vsize = 8388608, load factor = 0.9990, insert/erase = 251/1009 ms
```
## Benchmark Results
Some benchmark results have been uploaded, using various hash map implementations (`martinus`, `ska`, `phmap`, `dense_hash_map`, etc.) compiled and tested.
- **Benchmark Code:**
- [Bench All](https://github.com/ktprime/emhash/blob/master/bench/em_bench.cpp)
- [Bench High Load](https://github.com/ktprime/emhash/blob/master/bench/martin_bench.cpp)
- **Interactive Benchmark Results:**
- **[Charts with Performance Curves](https://github.com/ktprime/emhash/blob/master/bench/tsl_bench/chartsAll.html)**
*(Download all JavaScript files from the `tsl_bench` directory to view properly.)*
Generated using the [Tessil Benchmark Code](https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html).
- **Raw Benchmark Data:**
- [martin_bench.txt](https://github.com/ktprime/emhash/blob/master/bench/martin_bench.txt)
*(Generated using [martinus/map_benchmark](https://github.com/martinus/map_benchmark))*.
The benchmark code has been slightly modified to accommodate additional hash maps. The results are **not absolute** as they depend on OS, CPU, compiler, and dataset input.
### Tested Environments
Benchmarks were performed on:
- **3 Linux servers** (AMD, Intel, ARM64)
- **Windows 10** PC & laptop
- **Apple M1**
#### Performance Comparison (Lower is Better)







---
## Limitations & Known Issues
### 1. Not a Node-Based Hash Map
`emhash` does **not** maintain reference stability during insert/erase/rehash operations. If stability is required, use a pointer-based approach or choose a node-based hash map.
**Incorrect usage example:**
```cpp
emhash7::HashMap myhash(10);
myhash[1] = 1;
auto& myref = myhash[1]; // ❌ Reference is unstable
auto old = myref; // ❌ `myref` may become invalid after rehash
```
**Potential crash scenario (rehash during insertion):**
```cpp
emhash7::HashMap myhash2;
for (int i = 0; i < 10000; i++)
myhash2[rand()] = myhash2[rand()]; // ❌ May crash due to rehashing
// ✅ Use `reserve()` before inserting large amounts of data:
myhash2.reserve(20000);
```
---
### 2. Handling Large Key-Value Pairs
For very large values (e.g., `sizeof(value) > 100 bytes`), **using pointers instead of direct values** can help reduce memory overhead.
**Memory-intensive approach:**
```cpp
emhash7::HashMap myhash; // `valueT` is large (e.g., 100 bytes)
```
**Optimized memory usage:**
```cpp
emhash7::HashMap myhash2; // Use pointer
```
or
```cpp
emhash7::HashMap> myhash3;
```
---
### 3. Known Bug: Erasing During Iteration
If a key/iterator is erased during iteration, one key may be iterated **twice or missed**. Fixing this issue reduces performance by **20% or more**, and there is currently no efficient fix.
**Incorrect (may cause iteration issues):**
```cpp
emhash7::HashMap myhash;
for (const auto& it : myhash) {
if (some_key == it.first) {
myhash.erase(some_key); // ❌ No break after erase
}
do_some_more();
}
```
**Corrected version (use iterator-based erase):**
```cpp
for (auto it = myhash.begin(); it != myhash.end();) {
if (some_key == it->first) {
it = myhash.erase(it); // ✅ Correct: assign back to iterator
} else {
++it;
}
do_some_more();
}
```
**Another common mistake (invalid iterator use after erase):**
```cpp
emhash7::HashMap myhash = {{1, 2}, {5, 2}};
auto it = myhash.find(1);
it = myhash.erase(it); // ✅ Correct
myhash.erase(it++); // ❌ Error: `it` is already erased
```
---
## Which Hash Map Should You Choose?
The best choice depends on your use case. Here are some general recommendations:
1. **For complex/big keys & values (e.g., `std::string` or user-defined structs)**
- **Use `emhash8`**
- Benefits:
- Memory layout is contiguous (like `std::vector`), resulting in **fast iteration speed**.
- **Fast search & insertion**, **low memory usage**.
- Drawback:
- Slightly slower deletion compared to `emhash5-7`.
2. **For insertion-heavy workloads**
- **Use `emhash7`**
- Benefits:
- Supports an **extremely high load factor** (`>0.90`, even `0.99`).
- **Efficient memory usage**.
3. **For fast search & erasure with integer keys**
- **Use `emhash5/6`**
- Benefits:
- Optimized for **find-hit** and **erasure** with integer keys.
- Can be used as a **small stack hashmap** by defining `EMH_SMALL_SIZE`.
---
## Benchmark Environment
The following benchmarks were performed on an **AMD 5800H CPU (Windows 10, GCC 11.3)** using:
[Benchmark Code](https://github.com/ktprime/emhash/blob/master/bench/qbench.cpp)
---
|10 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 32.6| 26.0| 27.7| 35.6| 23.8| 62.5 |
|emhash8::HashMap| 46.5| 27.5| 29.5| 29.2| 23.2| 62.5 |
|emhash7::HashMap| 38.0| 27.6| 29.1| 27.9| 23.9| 62.5 |
|emhash6::HashMap| 38.9| 27.2| 28.8| 28.1| 23.9| 62.5 |
|emhash5::HashMap| 41.3| 27.2| 28.2| 27.8| 28.7| 62.5 |
|200 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 12.4| 3.19| 7.64| 9.56| 2.39| 78.1 |
|emhash8::HashMap| 15.0| 5.14| 6.40| 7.22| 1.17| 78.1 |
|emhash7::HashMap| 12.4| 5.09| 5.92| 4.95| 1.88| 78.1 |
|emhash6::HashMap| 12.8| 4.96| 6.77| 5.67| 1.89| 78.1 |
|emhash5::HashMap| 15.8| 4.88| 5.79| 5.43| 3.54| 78.1 |
|3000 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 9.67| 1.90| 4.98| 7.16| 1.33| 73.2 |
|emhash8::HashMap| 13.5| 4.00| 5.38| 6.21| 0.08| 73.2 |
|emhash7::HashMap| 10.6| 3.98| 4.88| 3.93| 0.76| 73.2 |
|emhash6::HashMap| 11.4| 3.81| 5.70| 4.78| 0.76| 73.2 |
|emhash5::HashMap| 14.3| 3.82| 4.80| 4.50| 2.94| 73.2 |
|40000 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 10.7| 2.82| 2.85| 8.47| 1.43| 61.0 |
|emhash8::HashMap| 16.0| 4.22| 6.01| 7.14| 0.00| 61.0 |
|emhash7::HashMap| 11.7| 4.19| 5.47| 4.44| 0.73| 61.0 |
|emhash6::HashMap| 13.0| 3.89| 5.86| 5.24| 0.73| 61.0 |
|emhash5::HashMap| 15.7| 3.93| 5.30| 5.01| 4.40| 61.0 |
|500000 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 20.0| 13.3| 3.99| 17.9| 1.58| 47.7 |
|emhash8::HashMap| 26.2| 6.39| 7.62| 9.17| 0.00| 47.7 |
|emhash7::HashMap| 18.3| 10.6| 7.79| 9.82| 0.78| 47.7 |
|emhash6::HashMap| 21.6| 8.74| 8.39| 9.82| 0.79| 47.7 |
|emhash5::HashMap| 24.5| 8.19| 9.93| 8.93| 6.16| 47.7 |
|7200000 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 50.3| 21.3| 24.8| 29.6| 0.97| 85.8 |
|emhash8::HashMap| 82.4| 18.5| 18.7| 39.2| 0.00| 85.8 |
|emhash7::HashMap| 51.2| 18.4| 21.1| 20.2| 0.63| 85.8 |
|emhash6::HashMap| 53.5| 16.7| 19.2| 26.0| 0.63| 85.8 |
|emhash5::HashMap| 62.6| 16.0| 19.7| 22.6| 1.60| 85.8 |
|10000000 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 79.4| 32.3| 17.7| 48.0| 1.45| 59.6 |
|emhash8::HashMap| 95.6| 18.3| 18.1| 46.3| 0.00| 59.6 |
|emhash7::HashMap| 66.0| 16.9| 18.1| 20.4| 0.74| 59.6 |
|emhash6::HashMap| 63.6| 15.1| 17.0| 24.0| 0.75| 59.6 |
|emhash5::HashMap| 64.7| 16.6| 18.8| 22.3| 4.75| 59.6 |
|50000000 hashmap|Insert|Fhit |Fmiss|Erase|Iter |LoadFactor|
|----------------|------|-----|-----|-----|-----|----------|
|emilib2::HashMap| 79.4| 31.2| 27.7| 55.1| 1.22| 74.5 |
|emhash8::HashMap| 93.1| 19.4| 20.1| 41.7| 0.00| 74.5 |
|emhash7::HashMap| 62.7| 20.3| 22.4| 25.2| 0.68| 74.5 |
|emhash6::HashMap| 61.5| 16.1| 18.0| 28.0| 0.67| 74.5 |
|emhash5::HashMap| 65.1| 16.1| 18.8| 24.3| 2.72| 74.5 |