A fast & densely stored hashmap and hashset for C++17 and later.
The classes ankerl::unordered_dense::map and ankerl::unordered_dense::set are (almost) drop-in replacements of std::unordered_map and std::unordered_set. While they don't have as strong iterator / reference stability guarantees, they are typically much faster.
Additionally, there are ankerl::unordered_dense::segmented_map and ankerl::unordered_dense::segmented_set with lower peak memory usage, and stable references (iterators are NOT stable) on insert.
- 1. Overview
- 2. Installation
- 3. Usage
- 3.1. Modules
- 3.2. Hash
- 3.3. Container API
- 3.3.1.
auto replace_key(iterator it, K&& new_key) -> std::pair<iterator, bool> - 3.3.2.
auto extract() && -> value_container_type - 3.3.3.
extract()Single Elements - 3.3.4.
[[nodiscard]] auto values() const noexcept -> value_container_type const& - 3.3.5.
auto replace(value_container_type&& container) - 3.3.6.
auto hash_for(K const& key) const -> precomputed_hash - 3.3.7.
auto visit(FwdIt first, FwdIt last, F f) -> size_t
- 3.3.1.
- 3.4. Custom Container Types
- 3.5. Custom Bucket Types
- 3.6. Disabling the Vector Probe
- 3.7. LLDB Data Formatters
- 4.
segmented_mapandsegmented_set - 5. Design
- 6. Real World Usage
- 6.1. Databases and data engines
- 6.2. Games, emulators and game engines
- 6.3. Graphics, rendering and GPU compute
- 6.4. Maps and geospatial
- 6.5. CAD, 3D printing and simulation
- 6.6. Bioinformatics
- 6.7. Networking, media and security
- 6.8. Finance and blockchain
- 6.9. Tools, libraries and machine learning
- 6.10. Ports
The chosen design has a few advantages over std::unordered_map:
- Perfect iteration speed - Data is stored in a
std::vector, all data is contiguous! - Very fast insertion & lookup speed, in the same ballpark as
absl::flat_hash_map - Low memory usage
- Full support for
std::allocators, and polymorphic allocators. There areankerl::unordered_dense::pmrtypedefs available - Customizable storage type: with a template parameter you can e.g. switch from
std::vectortoboost::interprocess::vectoror any other compatible random-access container. - Better debugging: the underlying data can be easily seen in any debugger that can show an
std::vector.
There's no free lunch, so there are a few disadvantages:
- Deletion speed is relatively slow. This needs two lookups: one for the element to delete, and one for the element that is moved onto the newly empty spot.
- no
const Keyinstd::pair<Key, Value> - Iterators and references are not stable on insert or erase.
The default installation location is /usr/local.
Clone the repository and run these commands in the cloned folder:
mkdir build && cd build
cmake ..
cmake --build . --target installConsider setting an install prefix if you do not want to install unordered_dense system wide, like so:
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX:PATH=${HOME}/unordered_dense_install ..
cmake --build . --target installTo make use of the installed library, add this to your project:
find_package(unordered_dense CONFIG REQUIRED)
target_link_libraries(your_project_name unordered_dense::unordered_dense)ankerl::unordered_dense supports c++20 modules. Simply compile src/ankerl.unordered_dense.cpp and use the resulting module, e.g. like so:
clang++ -std=c++20 -I include --precompile -x c++-module src/ankerl.unordered_dense.cpp
clang++ -std=c++20 -c ankerl.unordered_dense.pcmTo use the module, for example in module_test.cpp, use
import ankerl.unordered_dense;and compile with e.g.
clang++ -std=c++20 -fprebuilt-module-path=. ankerl.unordered_dense.o module_test.cpp -o mainA simple demo script can be found in test/modules.
The module compares fingerprints without SSE2, see 3.6. Disabling the Vector Probe. If you wrap the header in a module of your own and build it with gcc, you need to do the same.
ankerl::unordered_dense::hash is a fast and high quality hash, based on wyhash. The ankerl::unordered_dense map/set differentiates between high quality hashes (good avalanching effect) and low quality hashes. High quality hashes contain a special marker:
using is_avalanching = void;This is the case for the specializations bool, char, signed char, unsigned char, char8_t, char16_t, char32_t, wchar_t, short, unsigned short, int, unsigned int, long, long long, unsigned long, unsigned long long, T*, std::unique_ptr<T>, std::shared_ptr<T>, enum, std::basic_string<C>, and std::basic_string_view<C>.
Hashes that do not contain this marker are assumed to be of low quality and receive an additional mixing step inside the map/set implementation. The marker can also be spelled using is_avalanching = std::true_type;, and given for a hash you cannot edit — see 3.2.7.
Consider a simple custom key type:
struct id {
uint64_t value{};
auto operator==(id const& other) const -> bool {
return value == other.value;
}
};The simplest implementation of a hash is this:
struct custom_hash_simple {
auto operator()(id const& x) const noexcept -> uint64_t {
return x.value;
}
};This can be used, for example, with
auto ids = ankerl::unordered_dense::set<id, custom_hash_simple>();Since custom_hash_simple doesn't have a using is_avalanching = void; marker, it is considered to be of low quality and additional mixing of x.value is automatically provided inside the set.
Back to the id example, we can easily implement a higher quality hash:
struct custom_hash_avalanching {
using is_avalanching = void;
auto operator()(id const& x) const noexcept -> uint64_t {
return ankerl::unordered_dense::detail::wyhash::hash(x.value);
}
};We know wyhash::hash is of high quality, so we can add using is_avalanching = void; which makes the map/set directly use the returned value.
Instead of creating a new class you can also specialize ankerl::unordered_dense::hash:
template <>
struct ankerl::unordered_dense::hash<id> {
using is_avalanching = void;
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
return detail::wyhash::hash(x.value);
}
};This map/set supports heterogeneous overloads as described in P2363 Extending associative containers with the remaining heterogeneous overloads which is targeted for C++26. This has overloads for find, count, contains, equal_range (see P0919R3), erase (see P2077R2), and try_emplace, insert_or_assign, operator[], at, and insert & emplace for sets (see P2363R3).
For heterogeneous overloads to take effect, both hasher and key_equal need to have the attribute is_transparent set.
Here is an example implementation that's usable with any string type that is convertible to std::string_view (e.g. char const* and std::string):
struct string_hash {
using is_transparent = void; // enable heterogeneous overloads
using is_avalanching = void; // mark class as high quality avalanching hash
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}
};To make use of this hash you'll need to specify it as a type, and also a key_equal with is_transparent like std::equal_to<>:
auto map = ankerl::unordered_dense::map<std::string, size_t, string_hash, std::equal_to<>>();For more information see the examples in test/unit/transparent.cpp.
When an implementation for std::hash of a custom type is available, it is automatically used and assumed to be of low quality (thus std::hash is used, but an additional mixing step is performed).
If your std::hash specialization is a high quality one, say so there and it is taken at its word — the extra mixing is then skipped, exactly as for a hash written in ankerl::unordered_dense. The fallback asks hash_is_avalanching like everything else, so either spelling of the marker works, and a std::hash you cannot edit can be named from outside (3.2.7):
template <>
struct std::hash<id> {
using is_avalanching = void;
auto operator()(id const& x) const noexcept -> size_t {
return ankerl::unordered_dense::detail::wyhash::hash(x.value);
}
};When the type has a unique object representation (no padding, trivially copyable), one can just hash the object's memory. Consider a simple class
struct point {
int x{};
int y{};
auto operator==(point const& other) const -> bool {
return x == other.x && y == other.y;
}
};A fast and high quality hash can be easily provided like so:
struct custom_hash_unique_object_representation {
using is_avalanching = void;
[[nodiscard]] auto operator()(point const& f) const noexcept -> uint64_t {
static_assert(std::has_unique_object_representations_v<point>);
return ankerl::unordered_dense::detail::wyhash::hash(&f, sizeof(f));
}
};using is_avalanching = void; is a member of the hash, which is no help when the hash comes from a library you cannot edit. hash_is_avalanching is what the map and set actually ask, and it can be answered from outside:
template <>
struct ankerl::unordered_dense::hash_is_avalanching<their::good_hash> : std::true_type {};The extra mixing is now skipped for their::good_hash everywhere, without touching it. The specialization also works the other way — std::false_type makes the map mix a hash's output whatever the hash claims about itself, which is the escape hatch for one that promises more than it delivers.
This is deliberately the same name, the same two ways of answering, and the same meaning as boost::hash_is_avalanching, so a hash annotated for Boost.Unordered is read correctly here and the other way around. The member may therefore also be written as a compile time bool, which is the spelling Boost's documentation asks for:
using is_avalanching = std::true_type; // same as `= void`
using is_avalanching = std::false_type; // says the oppositeBoost calls = void deprecated; here it stays the ordinary spelling, since it is what this library has always documented and what every hash in the header uses. Writing anything else there — a stray int, say — is a compile error rather than a silent yes or no.
In a codebase where every hash is meant to be a high quality one, forgetting to say so is the easy mistake, and nothing complains — the map just quietly mixes. Wrap the hash to make it a build error instead:
template <class Key, class T>
using my_map = ankerl::unordered_dense::map<Key, T, ankerl::unordered_dense::require_avalanching<my_hash<Key>>>;The requirement is written into the alias rather than next to the hash, so it is part of what my_map is, and survives my_hash being reimplemented without its marker — which a static_assert next to the hash does not. (It does not follow a map that is given a different hash outright: map<K, V, other_hash> names no requirement, so there is none.) It accepts a hash marked either way, by its own member typedef or by a hash_is_avalanching specialization.
The hash must not be final, since the wrapper derives from it — for one that is, specialize hash_is_avalanching instead. A stateful hash goes in either braced or by value: require_avalanching<my_hash>{my_hash{seed}}.
In addition to the standard std::unordered_map API (see https://en.cppreference.com/w/cpp/container/unordered_map), we have additional API that is somewhat similar to the node API, but leverages the fact that we're using a random access container internally:
Updates the key of an element in-place without changing its position in the underlying container. This operation maintains iterator and reference stability - all existing iterators and references remain valid after the update.
Note that this can also be used as an optimization for unordered_dense::set when you want to erase one element and then insert a new element, this should be quite a bit faster.
Extracts the internally used container. *this is emptied.
Similar to erase(), there is an API call extract(). It behaves exactly the same as erase, except that the return value is the moved element that is removed from the container:
auto extract(const_iterator it) -> value_typeauto extract(Key const& key) -> std::optional<value_type>template <class K> auto extract(K&& key) -> std::optional<value_type>
Note that the extract(key) API returns an std::optional<value_type> that is empty when the key is not found.
Exposes the underlying values container.
Discards the internally held container and replaces it with the one passed. Non-unique elements are removed, and the container will be partly reordered when non-unique elements are found.
Hashing a key is usually the largest part of a lookup, and looking up the same key over and over hashes it every time. hash_for() does it once, and find, contains, count, equal_range and at each take what it returns as a second argument:
auto map = ankerl::unordered_dense::map<std::string, int>();
// ...
// hash it once, e.g. at startup
auto const status_hash = map.hash_for("status");
// as often as you like
auto it = map.find("status", status_hash);The key is still needed — a lookup that lands on a bucket still has to compare keys to know it found the right one. What is skipped is the hashing, so the longer the key the more there is to gain (clang 18, x86-64, half hits and half misses):
| key length | find(key) |
find(key, hash) |
|
|---|---|---|---|
| 8 bytes | 5.6 ns | 4.0 ns | 1.4x |
| 32 bytes | 7.1 ns | 4.3 ns | 1.7x |
| 200 bytes | 22.5 ns | 7.4 ns | 3.0x |
precomputed_hash is a distinct type rather than a plain integer, because the number a lookup wants is not what hash_function() returns — the table finalizes that further — and an integer parameter would happily accept the wrong one. An integer does not convert to it; the value inside stays reachable, so a hash can be stored or moved around freely.
A hash belongs to the hasher, not to the table it came from. It stays valid across insertions, erasures, rehash() and moves, and every table using the same hasher takes it — so one hash can serve a map and a set together:
auto set = ankerl::unordered_dense::set<std::string>();
auto found = set.find("status", status_hash); // the hash from the map aboveWhat it does not survive is the key changing. Looking up a key with the hash of a different key does not throw or crash — it just quietly reports the key as not present.
Heterogeneous lookup works as usual when the hash and equality are transparent, and the hash may be taken from one key type and used with another:
auto const h = map.hash_for(std::string_view("status"));
auto it = map.find("status"s, h);Only lookups take a precomputed hash, and insertion never will: a lookup given the wrong hash merely misses, while an insertion given one files the element under a probe chain it is not on, losing it for good and letting a second copy of the same key in beside it. Erase is left out for a duller reason — it hashes the moved element as well as the key, so precomputing the key's hash would save it only half its hashing.
Looks up a whole range of keys, calls f on each one that is there, and returns how many that was.
auto const keys = std::vector<std::string>{"alpha", "beta", "gamma"};
auto total = 0;
auto found = map.visit(keys.begin(), keys.end(), [&](auto const& kv) { total += kv.second; });f receives value_type&, or value_type const& on a const map, so a visit can modify what it finds. Keys that are absent are not reported; the count says how many were there.
Why it is faster than the same loop of find(). A lookup on a table past the cache is two dependent memory accesses — the group's block, and then the value the slot points at — and a loop doing one lookup at a time can only overlap them as far as the processor's own reordering reaches past a whole loop body. visit works a chunk at a time in three passes: every key's block is asked for, then the fingerprints are matched once the blocks have arrived, then the keys are compared. Every block in the chunk is in flight at once.
map<uint64_t, size_t>, clang 22 on a 7950X, ns per lookup, against the same batch looked up one key at a time:
| entries | one at a time | visit |
||
|---|---|---|---|---|
| 4 000 000 | all hits | 34.0 | 26.3 | 1.29x |
| 4 000 000 | half hits | 34.4 | 28.4 | 1.21x |
| 16 000 000 | all hits | 36.5 | 30.4 | 1.20x |
| 16 000 000 | half hits | 37.3 | 31.5 | 1.18x |
It needs a table past the cache to be worth anything, like every other memory-level trick here: on a map that fits in L2 there is nothing to overlap and the extra passes are a small loss.
And the batching itself matters more than visit does. If the keys are being fetched from somewhere in the same loop that looks them up — a random index into another array, say — then the key's own cache miss sits in front of the map's and neither overlaps with anything. Collecting the keys first and looking them up afterwards is worth 1.5x at four million entries before visit is involved at all:
for (size_t i = 0; i < n; ++i) { // 51 ns per lookup
auto it = map.find(keys[indices[i]]);
}
std::vector<key_type> batch; // 33 ns per lookup
for (size_t i = 0; i < n; ++i) { batch.push_back(keys[indices[i]]); }
for (auto const& k : batch) { auto it = map.find(k); }That is a property of loops and memory parallelism rather than of this map, and it is the larger of the two effects. visit is what is left on top once the loop is already shaped that way.
unordered_dense accepts a custom allocator, but you can also specify a custom container for that template argument. That way it is possible to replace the internally used std::vector with e.g. std::deque or any other container like boost::interprocess::vector. This supports fancy pointers (e.g. offset_ptr), so the container can be used with e.g. shared memory provided by boost::interprocess.
The index is groups of sixteen slots; the bucket type chooses how wide a value index is. The default should be good for pretty much everyone. See 5. Design for how the index works.
- Up to 2^32 = 4.29 billion elements.
- 5.5 bytes overhead per slot: one 88 byte block per group of sixteen slots, holding the sixteen fingerprints, the group's eight overflow counters and sixteen 4 byte value indices.
- Up to 2^63 = 9,223,372,036,854,775,808 elements.
- 9.5 bytes overhead per slot: the same block with 8 byte value indices instead of 4 byte ones, so 152 bytes per group.
A probe compares a group's sixteen fingerprints at once: with one SSE2 instruction on x86-64, and
with NEON on AArch64. Neither needs a compiler flag, because both are part of their target's
baseline. Defining ANKERL_UNORDERED_DENSE_HAS_SSE2 or ANKERL_UNORDERED_DENSE_HAS_NEON to 0
before the header is included switches to the portable fallback, which compares eight fingerprints
per machine word with ordinary arithmetic, e.g. with cmake:
target_compile_definitions(your_target PRIVATE ANKERL_UNORDERED_DENSE_HAS_SSE2=0)The module in src/ sets both already, because the intrinsics are declared by headers included in
the global module fragment and those declarations do not reach a translation unit that imports the
module. Wrapping the header in a module of your own means doing the same.
All three compare the same sixteen bytes and differ only in how they report which lanes matched, so translation units that disagree about these macros still agree about every byte of the index they share. On x86-64 the word-at-a-time fallback is within a few percent of SSE2 on the benchmark's workloads; on AArch64 it is not, which is why the NEON path exists.
The repository ships a formatter script for LLDB in lldb/unordered_dense.py. It makes
map, set, segmented_map, segmented_set (including the pmr:: variants) and segmented_vector print like
regular containers instead of raw internals, across map's every flavor with one provider:
(lldb) frame variable word_count
(ankerl::unordered_dense::map<std::string, int> &) word_count = size=3 bucket_count=64 {
[alpha] = (first = "alpha", second = 1)
[beta] = (first = "beta", second = 2)
[gamma] = (first = "gamma", second = 3)
}
Load it with
command script import /path/to/unordered_dense/lldb/unordered_dense.py
or put that line into ~/.lldbinit to always have it. Elements are the densely stored values in the container's
iteration order — insertion order until something is erased — and children are named after their key when the key
renders as a short scalar or string (v map[2] works by index regardless). The script only reads memory, so it is
safe on core dumps, and frame variable -R <var> still shows the raw members whenever they are wanted. Naming
children by key can be turned off with
script unordered_dense.NAME_CHILDREN_BY_KEY = False
Custom value containers (see 3.4) fall back to whatever LLDB itself can display for them.
ankerl::unordered_dense provides a custom container implementation that has lower memory requirements than the default std::vector. Memory is not contiguous, but it can allocate segments without having to reallocate and move all the elements. In summary, this leads to
- Much smoother memory usage of the values, which increases continuously.
- No high peak memory usage from the values.
- Faster insertion because elements never need to be moved to newly allocated blocks
- Slightly slower indexing compared to
std::vectorbecause an additional indirection is needed.
Here is what each of four maps holds while 10 million uint64_t -> uint64_t pairs are inserted into it:

| inserting 10M pairs | held at the end | peak while filling |
|---|---|---|
ankerl::unordered_dense::map |
361 MB | 495 MB |
ankerl::unordered_dense::segmented_map |
253 MB | 253 MB |
boost::unordered_flat_map |
268 MB | 403 MB |
absl::flat_hash_map |
285 MB | 428 MB |
Every flat and dense map in that chart has the same sawtooth, and for the same reason: growing means allocating the new array before releasing the old one, so the transient is what a caller has to have room for even though nothing ever reports it. ankerl::unordered_dense::map has the tallest one, because a dense map grows a vector of values as well as an index.
segmented_map is the line without a sawtooth. Its values live in fixed-size segments, so growing adds a segment instead of copying everything into a bigger block, and the memory it holds only ever goes up. The one step still visible in that line is the index doubling, which segmenting does not remove -- but it happens while the values are still small, so on this run it never rises above where the map ends up, and the peak and the steady state are the same number.
The segmenting is about the values: it is those that grow smoothly and whose references stay valid. The index is one plain contiguous array either way, and growing it still allocates the new one beside the old. Since 5.0.0 that is a change from before, when the index was segmented too.
Each line runs to the end of its own fill and then drops to zero, which is that map being destroyed -- for the dense maps in two steps, the index and then the values. So where a line falls off is how long that map took to fill: 0.41 s for boost, 0.47 s for abseil, 0.50 s for segmented_map and 0.57 s for map on this machine. Do not read that as a build benchmark, though. This chart deliberately does not raise glibc's mmap threshold the way the benchmark suite does, so every large block here is faulted in from the kernel a page at a time, and it is measuring memory rather than speed.
The chart is drawn by scripts/ab/alloc_timeline.sh, which counts every allocation the process makes by replacing global operator new -- an allocator handed to a container sees only what that container asks for through it -- charges each one what the allocator really gave away (malloc_usable_size plus glibc's chunk header, so the rounding up is counted rather than guessed at), and takes a std::chrono::steady_clock reading at each change. The runtimes on the x axis are from one machine and one run; the byte counts are exact.
How much the remaining index spike matters depends on the size of your value. The index is 5.5 bytes per slot, so at the moment it doubles it needs about 16.5 bytes per slot transiently, against sizeof(value_type) bytes per element for the values. For map<uint64_t, uint64_t> that spike is roughly two thirds of the value storage; for a map with a large value it is a rounding error; for a set<uint64_t> it is larger than the values. If you need the index to grow smoothly as well, reserve() up front avoids the doubling entirely, which is worth doing for a large map whatever container it uses.
The map/set has two data structures:
std::vector<value_type>which holds all data. map/set iterators are juststd::vector<value_type>::iterator!- An indexing structure, which is a flat array of blocks. Each block is one group of sixteen slots: their fingerprints, the group's overflow counters, and the sixteen value indices, all in the same 88 bytes.
Whenever an element is added, it is emplace_backed to the vector. The key is hashed, and the index
records where the value went. The index is groups of sixteen slots:
struct block {
uint8_t m_fingerprints[16]; // the low byte of the hash, 0 means empty
uint8_t m_overflows[8]; // how many entries with (fingerprint & 7) == i probed past this group
uint32_t m_index[16]; // where in the value vector each occupied slot's element is
}; // 88 bytes, one per group, in a single arrayThe top bits of the hash pick the group, the low byte is the fingerprint, with 0 mapped to 8 so that 0 can mean "empty" and the low three bits, which select one of the eight counters, are unchanged. An insert takes the first free slot from the home group onwards, in a quadratic sequence over groups, and increments its counter in every full group it passed.
The key is hashed, the group's sixteen fingerprints are loaded at once and compared against the key's fingerprint in one instruction, and the result is a 16 bit mask of candidate slots. For each candidate the value index is read and the key in the data vector is compared; when equal, the value is returned. If no candidate matched, the one overflow counter that the fingerprint selects decides: zero means no entry with those bits ever left this group, so the key is absent, and otherwise the probe moves to the next group. It also gives up once it has visited every group, which is as far as any key that exists can have been placed. That bound matters: a counter counts the entries that overflowed past its group on their probe sequences, so with a hash the caller controls every group's counter can be left positive by a handful of keys, and a miss would then have nothing on its sequence to stop at.
A slot's value index sits at a fixed offset from the fingerprints it belongs to rather than in a second array at a second address, so a lookup touches one region instead of two and the line the index is on is prefetched while the fingerprints are still on their way.
An element that arrived while its home group was full sits in a later group, and it stays there even after the home group empties again. So a long-churned table probes a little further than one built from the same contents, and not by much: at a load of 0.76, after 200 full turnovers, 1.036 groups per hit against a fresh 1.031, and 1.061 per miss against 1.052. At a load of 0.79 it is 1.058 against 1.037 and 1.109 against 1.077. It settles there rather than growing, which is the difference from a design that leaves tombstones behind and has to rehash them away.
A lookup that finds something inside an operation that writes -- operator[], try_emplace,
insert -- puts that element back in its home group if there is room, which costs a load and two
stores and needs no second hash, since the probe just computed the home. That takes the drift back
and then some: one such lookup per erase-and-insert round leaves the churned table at 1.023 groups
per hit and 1.036 per miss, better than freshly built, because it also pulls home the elements the
original build left away from home. A workload that only reads gets none of this, and rehash()
rebuilds the index if you want the difference back that way.
Without a vector compare the same sixteen bytes are compared eight at a time with ordinary arithmetic, see 3.6. Disabling the Vector Probe.
Since all data is stored in a vector, removals are a bit more complicated:
- First, look up the element to delete in the index.
- Clear its fingerprint, and decrement the overflow counter in every group between its home group and the one it landed in. An erase undoes exactly what the insert did, so there are no tombstones and no rehash is ever needed to repair the index. Nothing else moves.
- Replace that element in the vector with the last element in the vector.
- Update the slot of the moved element, which requires another lookup.
Open source projects that use this map, grouped by what they do. The list was first put together on 2023-09-10 and last refreshed on 2026-08-06; every entry was confirmed by finding the include or the namespace in the project's own source on its default branch. Some authors have written in, the rest come from searching GitHub. Please send me a note if you want to be on that list!
- AliSQL - A MySQL branch originated from Alibaba Group.
- ArcticDB - A high performance, serverless DataFrame database built for the Python Data Science ecosystem.
- Bodo - A high performance compute engine for Python data processing.
- Milvus - A high-performance, cloud-native vector database built for scalable vector search.
- MySQL - Binary log transaction dependency tracking has used this map since 8.4.3 and 9.1.0, replacing a tree for the writeset history and taking about 60% less space for it.
- Percona Server - A free, fully compatible, enhanced and open source drop-in replacement for MySQL.
- Percona XtraBackup - Open source hot backup tool for InnoDB and XtraDB databases.
- RonDB - A distribution of NDB Cluster for real-time applications with high availability.
- Citron - A Nintendo Switch emulator.
- CrystalEngine - A Vulkan game engine with FrameGraph, PBR rendering and a declarative UI framework.
- DevilutionX - Diablo build for modern operating systems.
- FEX - A fast usermode x86 and x86-64 emulator for Arm64 Linux.
- FOnline Engine - A flexible cross-platform isometric game engine for multiplayer games.
- HiveWE - A Warcraft III World Editor (WE) that focusses on speed and ease of use.
- impacto - A reimplementation of the "MAGES." visual novel engine.
- LandSandBoat - A server emulator for Final Fantasy XI.
- Marathon Recompiled - An unofficial PC port of the Xbox 360 version of Sonic the Hedgehog (2006), created via static recompilation.
- Nazara Engine - A cross-platform framework aimed at (but not limited to) real-time applications and games.
- NVGT - The Nonvisual Gaming Toolkit, a cross-platform audio game engine.
- Oxylus Engine - A data-driven Vulkan game engine built in C++.
- Project Alice - An open source recreation of the grand strategy game Victoria II.
- Unleashed Recompiled - An unofficial PC port of the Xbox 360 version of Sonic Unleashed, created via static recompilation.
- Visual Pinball - An open source pinball table editor and simulator.
- AdaptiveCpp - Compiler for multiple programming models (SYCL, C++ standard parallelism) for CPUs and GPUs from all vendors.
- CyberFSR2 - Drop-in DLSS replacement with FSR 2.0 for various games such as Cyberpunk 2077.
- D3D12_Research - A hobby project to experiment with various modern rendering techniques in DirectX 12.
- LuisaCompute - High-performance rendering framework on stream architectures.
- NVIDIA MDL SDK - The NVIDIA Material Definition Language SDK, for physically based material definitions in rendering applications.
- OptiScaler - Bridges upscaling and frame generation across GPUs, supporting DLSS2+, XeSS and FSR2+ inputs.
- Skyrim Community Shaders - Community-driven advanced graphics modifications for Skyrim AE, SE and VR.
- Slang - A shading language that makes it easier to build and maintain large shader codebases in a modular and extensible fashion.
- WinUI - A modern UI framework with a rich set of controls and styles, the native UI layer of the Windows App SDK.
- Cloudini - A point cloud compression library, with ROS/PCL integration.
- CoMaps - Privacy-focused offline maps and navigation for Android and iOS, based on OpenStreetMap data.
- HDMapping - Open source software for mobile mapping, LiDAR odometry and point cloud registration.
- MapLibre Native - Interactive vector tile maps for iOS, Android and other platforms.
- Valhalla - Open source routing engine for OpenStreetMap data. Replaced robin-hood-hashing with this map and set in 3.6.0.
- Bambu Studio - PC software for BambuLab and other 3D printers.
- Lethe - Open-source computational fluid dynamics (CFD) software which uses high-order continuous Galerkin formulations to solve the incompressible Navier–Stokes equations (among others).
- PrusaSlicer - G-code generator for 3D printers (RepRap, Makerbot, Ultimaker etc.).
- web-ifc - Reading and writing IFC files with Javascript, at native speeds.
- GW - Genome browser and variant annotation tool for interactive visualisation of sequencing data.
- kallisto - Near-optimal RNA-Seq quantification.
- MashMap - A fast approximate aligner for long DNA sequences.
- metaMDBG - A lightweight assembler for long and accurate metagenomics reads.
- wfmash - Base-accurate DNA sequence alignments using WFA and mashmap3.
- Kismet - A sniffer, WIDS and wardriving tool for Wi-Fi, Bluetooth, Zigbee and RF, which runs on Linux and macOS.
- libossia - A modern C++, cross-environment distributed object model for creative coding and interaction scoring.
- mediasoup - Cutting edge WebRTC video conferencing SFU.
- ossia score - A free, open-source, cross-platform intermedia sequencer for precise and flexible scripting of interactive scenarios.
- Rspamd - Fast, free and open-source spam filtering system.
- YANET - A high performance framework for forwarding traffic based on DPDK.
- Cartesi Machine Emulator - The off-chain RISC-V emulator implementation of the Cartesi Machine.
- Monad - A high-performance EVM-compatible layer-1 blockchain client.
- opentxs - A free-software toolkit implementing the OTX protocol, together with a financial cryptography library, API, GUI, command-line interface and prototype notary server.
- RISC Zero - A zero-knowledge verifiable general computing platform based on RISC-V.
- WonderTrader - A one-stop quantitative research and trading framework.
- ArkScript - A small, fast, functional and scripting language for C++ projects.
- File Commander - A cross-platform Total Commander-like orthodox file manager for Windows, Mac and Linux.
- FlashTokenizer - An efficient and optimized BERT tokenizer engine for LLM inference serving.
- Ichor - A C++20 microservice bootstrapping framework focused on thread safety and dependency injection.
- minigpt4.cpp - Port of MiniGPT4 in C++ (4bit, 5bit, 6bit, 8bit, 16bit CPU inference with GGML).
- Nimble Commander - A dual-pane file manager for macOS.
- Operon - A modern C++ framework for symbolic regression that uses genetic programming to find the best-fitting model for a given regression target.
- PECOS - A versatile and modular machine learning framework for fast learning and inference on problems with large output spaces, such as extreme multi-label ranking and large-scale retrieval.
- PlotJuggler - The time series visualization tool that you deserve.
- PyOptInterface - Efficient modeling interface for mathematical optimization in Python.
- STP - Simple Theorem Prover, an efficient SMT solver for bitvectors.
- Tulip - Large graphs analysis, drawing and visualization framework.
Reimplementations of this design in other languages. They are not maintained here, and are listed because people have found them useful.
- HashMapC99 - A cache-efficient, densely stored hash map in C99, by Anılcan Gülkaya. Useful where a C++17 header is not an option, such as embedded targets, and for shorter compile times and smaller binaries.