emhash is a family of open addressing hash tables, and choosing the wrong one is easy
Fast and memory efficient c++ flat hash table/map/set
At a glance
- What is it?
- ktprime/emhash is an MIT licensed, header-only C++ hash table family that trades node allocation for open addressing with a single flat array, in several implementations that differ by workload. The README is explicit that emhash7 is the safe default, and the choice among emhash5 to emhash8 is the decision that matters.
- Who is it for?
- Adopt emhash if you have a hot map or set where allocation churn and cache misses are measurable, and if you are willing to pick an implementation deliberately, since the family exists precisely because the right choice depends on your workload pattern.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A family of tables, not one container
emhash is described as a family of high performance, header-only hash table implementations, and that word family is doing real work. The repository carries several parallel implementations, and the README refers to them by name throughout: hash_table5, hash_table6, hash_table7 and hash_table8, plus emilib1, emilib2 and emilib3 for the try_set and set_get helpers, and separate LRU caches in lru_size.hpp and lru_time.hpp. The version selection guide is built around this, and the guide is explicit that you should choose by primary workload pattern rather than by key type. The recommendation for people who are not sure is to start with emhash7, on the grounds that its no tombstone design keeps performance stable under mixed insert and erase workloads and that it has native high load factor support. In other words, the library is a menu, and the default is a recommendation rather than a single implementation being the only option.
Open addressing with a three way collision strategy
The mechanism underneath is open addressing with single array inline storage, which the README credits with minimising memory footprint and maximising cache hit rate. Collision handling is described as a smart three way hybrid strategy that combines linear probing, quadratic probing and bidirectional search, so different probes are used depending on the state of the table. The most distinctive property is the absence of tombstones: the README states that performance does not degrade even with frequent insert and erase operations, which is the usual weak point of open addressing designs, where erased slots poison the probe chains. Load factors are pushed very high, up to 0.999 through the EMH_HIGH_LOAD macro in hash_table5, 7 and 8, or through max_load_factor() in emhash6 and emhash7. The emhash8 implementation also added a zero-cost default constructor in v1.1.0, where empty maps share a static sentinel and allocate nothing until the first insert.
The compact layout is where the memory saving comes from
The memory story is a direct consequence of the flat layout, and the README gives a concrete figure rather than a vague claim. Significant memory savings appear when sizeof(key) % 8 differs from sizeof(value) % 8, and the worked example is that a hash_map of uint64_t keys with uint32_t values uses one third less memory than a hash_map of uint64_t to uint64_t. That is the trade of open addressing stated plainly: the elements live in one packed array, so padding that a node based map would carry per entry is amortised across the table. The other memory control is shrink_to_fit(), which releases unused memory after a table has been filled and then cleared. The README also documents an EMH_POD_HASH option, an opt-in byte hash for fixed size keys of 8, 12 or 16 bytes that are trivially copyable, enabled with a compiler definition.
The extra API is where the real speedups live
Beyond the standard container interface, the README lists a set of operations that exist to skip work. insert_unique inserts directly without a lookup, which the documentation calls a performance boost and which requires the key to be unique. try_get returns a pointer to the value or nullptr, which is more concise than an iterator dance. try_set sets the value only if the key already exists and does nothing otherwise, and set_get updates a value and returns the old one, both of which are listed for emhash5, emhash8 and the emilib headers. _erase is a faster delete that skips returning an iterator. Heterogeneous lookup is the most interesting of the set: find, contains, count, at and try_get are open templates on every implementation, so with a transparent hash and equality, that is is_transparent plus std::equal_to, they accept a std::string_view or a const char* without building a temporary std::string. Heterogeneous erase is emhash8 only, which is the kind of small inconsistency worth checking before you rely on it.
Adding it to a project takes one header or a CMake hook
Because the library is header-only, the cheapest integration is to copy a single file into your project:
# Option A: Copy directly (simplest, no build system needed)
cp include/emhash/hash_table7.hpp /your/project/emhash/Cloning the repository and pointing the compiler at the include directory works too:
# Option B: Clone and include
git clone https://github.com/ktprime/emhash.git
# Then add -I/path/to/emhash/include to your compiler flagsIf you already use CMake, FetchContent avoids an install step entirely:
# 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.3.0)
# FetchContent_MakeAvailable(emhash)
# target_link_libraries(your_target PRIVATE emhash::emhash)The include itself is a single line, with the version chosen by the file name:
#include "emhash/hash_table7.hpp" // Or emhash/hash_table[5-8].hppA working program then reserves space, inserts in three different ways and reads the values back:
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";
}Custom key types work by supplying a hash, and in C++20 the equality can be a lambda as well.
Choosing the implementation is the decision that matters
The selection guide is the most useful part of the documentation, and it is organised around one question. The quick version reads like this:
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)So the choice is not a matter of taste. emhash6 exists for integer keys because a bitmask can accelerate the search for an empty slot. emhash8 exists for iteration heavy use because its dense pairs array can be scanned sequentially, which matters when you walk the whole table. emhash7 is the general purpose answer, and its tombstone free design is what makes a mixed workload predictable. Note that this is a design guide, not a benchmark result, and the README also points at a third party benchmarks section, so the sensible move is to try the two candidates on your own data.
Toolchain support, third party code and the release cadence
The stated platform support is Windows, Linux and macOS, across x86_64 and ARM64 processors, naming Apple M1 and M2 along with AMD and Intel, and the compilers are GCC, Clang and MSVC at C++17, C++20 or C++23, with a badge confirming the standard range. The repository is a full project rather than a single header drop, with a CMakeLists.txt, a conanfile.py for Conan packaging, a config.cmake.in template, a Doxyfile, a bench directory, a tests directory, a thirdparty directory and a THIRDPARTY_LICENSES.md file, alongside scripts, docs, a CHANGELOG.md, a VERSION file, a SECURITY.md, build32.sh and the usual .clang-format and .clang-tidy configuration. The licence is MIT, and the releases are infrequent but deliberate: v1.2.0 on 2026-07-15 and v1.3.0 on 2026-10-15, with the last push on 2026-10-17. A version that jumps from 1.2 to 1.3 in three months is worth reading in the changelog before you pin one.
Editorial conclusion
Adopt emhash if you have a hot map or set where allocation churn and cache misses are measurable, and if you are willing to pick an implementation deliberately, since the family exists precisely because the right choice depends on your workload pattern. Do not adopt it as a drop-in default without reading the selection guide, because emhash6, emhash7 and emhash8 optimise for different mixes, and do not adopt it if you need standard allocator behaviour or a stable, published API contract across versions, since the README frames the library around throughput and layout. Before you commit, confirm your compiler is GCC, Clang or MSVC at C++17 or later, decide whether the third-party licences in THIRDPARTY_LICENSES.md are acceptable, and benchmark your own key type rather than trusting the published numbers.
Frequently asked questions
How do I add emhash to a C++ project?
The library is header-only, so you can copy a single header such as include/emhash/hash_table7.hpp into your project, clone the repository and add its include directory to the compiler flags, or pull it in with CMake FetchContent and link emhash::emhash. The README also documents a find_package route after an install.
Which emhash version should I use?
The README recommends emhash7 if you are not sure, because it has no tombstones and stays stable at high load factors. For find and erase heavy workloads with integer keys it points to emhash6, for string or large key and value types to emhash8, and for iteration heavy work to emhash8 as well.
Does emhash support heterogeneous lookup for string keys?
Yes. find, contains, count, at and try_get are open templates on every implementation, so with a transparent hash and equality such as is_transparent and std::equal_to, they accept a std::string_view or a const char* without building a temporary std::string. Heterogeneous erase is available on emhash8 only.
How much memory can emhash save compared to a node based map?
The README states significant savings when sizeof(key) % 8 differs from sizeof(value) % 8, and gives the example that a hash_map of uint64_t to uint32_t uses one third less memory than a hash_map of uint64_t to uint64_t. The layout is compact because all elements live in a single array.
Does emhash leave tombstones behind when you erase?
No. The README states that performance does not degrade even with frequent insert and erase operations, and it is the no tombstone design of emhash7 that makes it the recommended default for mixed workloads. shrink_to_fit is available to release unused memory afterwards.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ktprime-emhash)Was this useful?