Skip to main content

SmashTable

SmashTable makes one update span several containers. Either every change lands or none does, and the containers never disagree about which happened. It's implemented in C++ 20 as header-only templates, and exposed to Python 3 through the raw CPython API.

Three std::maps cannot do this, and neither can three dicts. It needs no threads to be useful — the same guarantee that keeps two indexes consistent under contention is what keeps them consistent when an exception unwinds a loop halfway through.

SmashTable Thumbnail

What's Inside

  basic_*            → cores, single-threaded     vector · AVL · weight-balanced · hash
  atomic_*           → pinned, GPU-capable        hash
       ↑ ::adopt() moves one allocation between the two, no rehash, no copy

  *_store            → transactions over a core   monotonic · snapshot · reference
       ↓ wrap any of those for thread safety
  locked_store       → one lock per transaction
  partitioned_store  → one lock per partition, 16 by default

  transaction_group  → one commit spanning several stores

Every store publishes what it promises as a compile-time isolation_k, and the suite checks that constant against behaviour rather than against itself — a container that quietly changes level fails. Each rung implies the ones beneath it, so one column names the highest a store reaches rather than five ticking the same fact five times. Every row below the cores wraps something: a transactional store wraps a plain container, and either thread-safety wrapper wraps a store. So a usable type reads locked_store<snapshot_store<basic_avl_tree<…>>> for one lock over the whole store, or partitioned_store<…> for sixteen. Nesting the two wrappers is redundant rather than clever: every call would take an inner lock inside a partition lock that already excludes.

Isolation Write concurrency Transactions Ordered
basic_vector — one thread — —
basic_avl_tree — one thread — ✔
basic_wb_tree — one thread — rank · select
basic_hash_table — one thread — —
atomic_hash_table — ⁴ per slot ⁴ — —
monotonic_store Monotonic Atomic View ⁶ one thread ✔ inherits
snapshot_store Snapshot ⁷ one thread ✔ inherits ²
serializable_store Serializable ⁸ one thread ✔ inherits ²
strict_serializable_store Strict Serializable ⁹ one thread ✔ inherits ²
reference_store ⁵ Monotonic Atomic View ⁶ one thread ✔ ✔
locked_store inherits one call ✔ inherits
partitioned_store inherits ¹ per partition ✔ inherits ³

¹ A stamp-based store keeps its level: one clock, and the watermark moves only once the last partition has published, so a reader sees a whole commit or none. A clock-less store has no stamp to hold, so only Read Committed survives; atomicity costs in proportion to the partitions touched. ² select and rank are exact at the newest commit; an older snapshot gets a merged walk, since one count per node cannot answer an unbounded parameter. ³ A bound, a range and an ordinal hold every partition's lock for the whole walk, so each is decided at one moment rather than by probes that can disagree. An unbounded enumeration and the cursor behind iteration take one partition at a time, unordered. ⁴ Per-slot spin locks: atomic over one slot and nothing wider, so size() is a relaxed read, a stalled thread blocks its slot, and there are no transactions. ⁵ A std::set-backed oracle, tested against rather than shipped. ⁶ Permits a lost update and a repeated read that moves. ⁷ Refuses both; permits write skew, since a read you did not watch is not validated. ⁸ Refuses write skew and phantoms too: every key and window read is re-checked at commit. ⁹ Same refusals as ⁸; strict also waits for its own publication, so a transaction opening after a commit cannot precede it — which changes only sharded commits and writes outside a transaction.

The ladder runs from Read Committed through Monotonic Atomic View and Snapshot Isolation to the two serializable rungs, and every level from Monotonic Atomic View upward is delivered by multi-version concurrency control: a key keeps one version per commit that touched it, a reader is answered from the newest version its own snapshot can name, and versions below the oldest live reader are reclaimed.

Python Quick Start

pip install smashtable

The wheel ships type stubs, so an editor completes the container methods and a checker rejects a misspelled isolation level or key layout before the call ever runs.

Two indexes over the same entities have to move together, or a lookup by name finds an identifier that no longer resolves:

import smashtable as st

by_id = st.SortedMap(key=int)     # entity id → record
by_name = st.SortedMap(key=str)   # name      → entity id

with st.transaction(by_id, by_name) as (ids, names):
    ids[42] = "carol"
    names["carol"] = 42
# both indexes became visible together

Each container is a mapping in its own right, so the usual vocabulary works:

list(by_id)                  # [42]           — keys, in order
dict(by_name)                # {'carol': 42}
by_id.items()                # a lazy view, not a copy
by_id.scan(0, 100)           # the half-open window [0, 100)

If the block raises, nothing was applied:

try:
    with st.transaction(by_id, by_name) as (ids, names):
        ids[43] = "dave"
        names["dave"] = 43
        raise ValueError("failed validation")
except ValueError:
    pass

assert 43 not in by_id and "dave" not in by_name

Without this, the alternative is an O(n) defensive copy that every alias to the container can still read through mid-update:

backup = dict(cache)                      # copies the whole thing
try:
    for key, value in updates.items():
        cache[key] = transform(value)     # raises on item 40 of 100
except Exception:
    cache.clear(); cache.update(backup)   # O(n) again, and readers already saw the torn state

Optimistic Concurrency

watch makes a transaction fail rather than overwrite a key someone else touched first. ConflictError is the retry signal, and the only exception a correct program is expected to catch routinely:

while True:
    try:
        with st.transaction(accounts) as (view,):
            view.watch("alice")
            view["alice"] = view["alice"] - 100
        break
    except st.ConflictError:
        continue    # nothing was applied, so retrying is safe

The Two Phases

with is exactly begin(), the body, stage(), commit(). Both phases are also available directly:

group = st.transaction(by_id, by_name)
ids, names = group.begin()
ids[42] = "carol"; names["carol"] = 42
group.stage()      # validates watches and reserves; still invisible, and may raise ConflictError
group.commit()     # flips visibility

Separating the fallible phase from the applying phase is what lets independent transactions compose into one all-or-nothing unit. stage is where a conflict or an allocation failure normally surfaces. commit validates again and can still refuse, because a watched key may be committed over while the group sits staged - and with more than one container that leaves the ones already published, so the group stays staged rather than finished, which is the state it can still be unwound from.

What It Guarantees, and What It Does Not

  • A group applies in full or not at all. A body that raises resets every participant. A stage that fails on one participant unwinds them all.
  • Staged writes are invisible to everyone else, including a transaction opened after the stage.
  • A transaction reads its own writes, until it stages. view[k] sees what view[k] = v put there; after stage() the write is in the store carrying no stamp, so it is invisible to everyone including its own transaction until commit().
  • Commit is not a snapshot. It applies each container in turn, so another thread reading two containers while a commit runs may find one of them a step ahead. A reader that needs the pair to agree should take its own transaction or read after the writer's block returns.
  • Scans are not snapshots either. scan() walks in key order and never yields a key twice or raises mid-walk, but a key inserted behind the cursor is missed. That is the container's own scan; the same call on a transaction participant records the window it read, so at serializable and above a key committed into that window refuses the commit rather than being silently missed.

Ordered and Unordered

SortedMap and SortedSet keep their keys in order, which is what iteration, scan and slice erase rest on. HashMap and HashSet are the same stores over an open-addressed core, and offer point access only:

cache = st.HashMap(key=str)
cache['a'] = 1
cache['a'], len(cache), 'a' in cache      # (1, 1, True)

cache.scan()                              # AttributeError — no ordering to scan
list(cache)                               # TypeError — not iterable

The absent methods are absent from the type rather than refused at the call, so reaching for one fails the way a typo fails. Everything else is shared — the same key layouts, the same isolation and sharing choices, the same transactions — so one st.transaction() may span ordered and unordered stores together.

Choosing an Isolation Level

A container names what it promises a reader, and reports back what it actually delivers:

cache = st.SortedMap(key=int)                                    # monotonic atomic view, the default
ledger = st.SortedMap(key=int, isolation='snapshot')
books = st.SortedMap(key=int, isolation='serializable')
audit = st.SortedMap(key=int, isolation='strict_serializable')

cache.isolation    # 'monotonic_atomic_view'
ledger.isolation   # 'snapshot'
books.isolation    # 'serializable'
audit.isolation    # 'strict_serializable'

Four names, refused by ValueError if misspelled — and the shipped type stubs turn a misspelling into a checker error before it ever runs.

Under snapshot every read a transaction makes is answered at the instant the transaction opened, so a repeated read returns what it first saw:

with st.transaction(ledger) as (view,):
    first = view[account]
    # ... another thread commits a new value for `account` here ...
    assert view[account] == first     # holds under snapshot, not under monotonic

That is what monotonic does not give you, and the reason to pay for the extra versions. The cost is memory: a key keeps every version a live reader can still name, and the tail is freed once the last transaction closes.

Snapshot validates what a transaction wrote, plus any key it explicitly watched, and nothing more, so two transactions that each read what the other writes and then write elsewhere both land — write skew, which Snapshot Isolation permits by construction because no two writes ever collide. serializable validates the read set as well, which is also what makes a phantom detectable: a window a transaction scanned is remembered as a window, so a key committed into it afterwards is a conflict even though no key either transaction touched overlaps.

with st.transaction(books) as (view,):
    empty = view.scan(20, 80)          # reads a window, and records that it did
    # ... another thread commits key 50 here ...
    ...                                # PhantomConflictError on leaving the block

PhantomConflictError derives from ConflictError, so a caller that retries on conflict needs no new branch. strict_serializable refuses exactly what serializable refuses; it adds only that a transaction opening after a commit returned cannot be ordered before it.

sharing decides how many writers can proceed at once:

st.SortedMap(key=int, sharing='locked')        # one lock, the default
st.SortedMap(key=int, sharing='partitioned')   # sixteen partitions, concurrent writers

isolation reports what a container delivers, not what it was asked for, and sharding is the one place those differ:

sharded_snapshot = st.SortedMap(key=int, isolation='snapshot', sharing='partitioned')
sharded_monotonic = st.SortedMap(key=int, isolation='monotonic_atomic_view', sharing='partitioned')

sharded_snapshot.isolation    # 'snapshot'        — asked for snapshot, got snapshot
sharded_monotonic.isolation   # 'read_committed'  — asked for monotonic, got less

A monotonic container has no stamp for a reader to hold, so a walk crossing partitions can catch a commit half-applied, and only Read Committed survives above a single key. A snapshot container does have one — visibility there is a comparison against a stamp rather than a lock somebody holds — so its commits become visible everywhere at once and its level survives however many partitions it is spread across.

Keeping the guarantee is not free, though. Making a commit atomic across partitions costs in proportion to how many of them a transaction touches: one writing a single key is unaffected, while one writing across all sixteen serializes against every other transaction that does the same. Sharding pays off for point-heavy work spread over many keys, and least for transactions that touch everything.

locked is the default because it is the setting under which the default level means what it says.

Stored Types

Values are int, float, bool, str and bytes, deep-copied into memory the library owns, so a stored value outlives the object it came from. str and bytes stay distinct, and bool round-trips as bool rather than as 1.

A container may instead hold arbitrary objects, which it stores by reference rather than by copy:

documents = st.SortedMap(key=str, value='object')
documents['doc'] = {'nested': [1, 2]}   # the very same object comes back out

The mode is named rather than inferred, because it is what the scalar guarantee rests on. In the default scalar mode nothing stored can touch a reference count, so the interpreter lock is released around every store; in object mode it is held, and the container is correspondingly slower.

Keys are typed and homogeneous. A container names its key layout at construction and refuses every other type:

st.SortedMap(key=int)      # signed 64-bit
st.SortedMap(key='uint')   # unsigned 64-bit, the only way to ask, since Python has no unsigned type
st.SortedMap(key=str)      # UTF-8, ordered bytewise
st.SortedMap(key=bytes)    # opaque, never decoded

That is what lets every comparison skip type dispatch: the ordering function is chosen once, at construction, rather than re-derived from a tag on each of the millions of comparisons a tree makes.

float and bool are values but never keys. A float key would make ordering depend on a total order over NaN, and bool is a subclass of int in Python, so accepting it would silently alias two key spaces:

m = st.SortedMap(key=int)
m[1] = 'one'      # fine
m[1.0] = 'one'    # TypeError: float is not a valid key
m[True] = 'one'   # TypeError: bool is not a valid key

This is the one place the library is deliberately stricter than dict, which treats 1, 1.0 and True as one key.

C++ Quick Start

include(FetchContent)
FetchContent_Declare(
    smashtable
    GIT_REPOSITORY https://github.com/ashvardanian/SmashTable
    GIT_TAG main
)
FetchContent_MakeAvailable(smashtable)
target_link_libraries(your_target PRIVATE smashtable::smashtable)

For a system-wide install, standard CMake package discovery works:

sudo cmake --install build_release
find_package(smashtable REQUIRED)
target_link_libraries(your_target PRIVATE smashtable::smashtable)

The library throws nowhere, and will not let you make it. Every API returns status_t, reads included, and a read delivers its answer through noexcept callbacks rather than through the return. A read is fallible from serializable_k up, where it has to write down what it read before the commit can validate it. A key whose copy or comparison can throw fails to compile rather than being quietly accepted. All containers take custom allocators and can be pre-allocated.

The same shape as the Python example, with no threads in sight:

#include <smashtable/basic_avl_tree.hpp>
#include <smashtable/monotonic_store.hpp>

namespace st = ashvardanian::smashtable;

using id_t = std::uint64_t;
using by_id_t = st::monotonic_avl_map<id_t, record_t>;      // id   → record
using by_name_t = st::monotonic_avl_map<name_t, id_t>;      // name → id

auto by_id = *by_id_t::make();
auto by_name = *by_name_t::make();

auto group = *st::make_transaction_group(by_id, by_name);
auto &ids = group.participant<0>();
auto &names = group.participant<1>();

_ = ids.upsert({id, record});
_ = names.upsert({record.name, id});
_ = ids.watch(id);                  // fail rather than clobber a concurrent write

_ = group.stage();                  // both reserve, or neither does
_ = group.commit();                 // both become visible

Staging the two by hand would leave the first one staged when the second refuses, which is the bug a group exists to remove. Participants are visited in order of the store's address rather than of the argument list, so two groups naming the same stores in opposite orders cannot deadlock on each other. A refused stage() rolls the staged prefix back rather than resetting it, so the writes survive and the group can be retried.

Basic containers — basic_avl_tree, basic_wb_tree — carry STL-style bidirectional iterators. Transactional containers do not, because keeping an iterator valid across concurrent updates costs more than it returns:

  • find(), lower_bound() and upper_bound() return status_t and take two noexcept callbacks, for the found and missing cases.
  • equal_range(), sample_one() and sample_reservoir() return status_t and take one callback, invoked per element.
  • find_copy(), lower_bound_copy() and upper_bound_copy() return an expected<value_t> for callers that cannot use a callback.

Why The C++ Library

The standard library has loose ends around failure. std::set::insert(first, last) has no defined behaviour on partial failure, and in practice an allocation failure leaves some elements inserted and the rest not:

struct failing_allocator_state_t {
    std::size_t count = 0, limit = 0;
};

template <typename value_type_>
struct failing_allocator { // ? rebind, converting constructor and `deallocate` elided
    using value_type = value_type_;
    failing_allocator_state_t *state_ {};

    value_type_ *allocate(std::size_t count) {
        if (!state_ || state_->count + count > state_->limit) throw std::bad_alloc {};
        state_->count += count;
        return static_cast<value_type_ *>(::operator new(count * sizeof(value_type_)));
    }
};

int main() {
    failing_allocator_state_t state {0, 3};
    std::set<int, std::less<>, failing_allocator<int>> values {std::less<> {}, failing_allocator<int> {&state}};
    std::vector<int> inputs {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    try { values.insert(inputs.begin(), inputs.end()); }
    catch (std::bad_alloc const &) { std::println("std::bad_alloc after {} allocations", state.count); }

    std::println("set contains {} elements:", values.size());
    for (int value : values) std::print("{} ", value);
    std::print("\n");
}

Under Clang++ 21 and G++ 15 that prints:

std::bad_alloc after 3 allocations
set contains 3 elements:
0 1 2

Where this matters is secondary-index consistency inside a storage engine — an id → slot map, a slot → vector map and a deleted set that have to move together, and where "the crash left index B disagreeing with index A" is a corruption bug someone has already debugged.

Stores

Terminology first, since both words are overloaded:

  • "Concurrency" does not imply threads. Several transactions can be open on one thread.
  • "Consistency" does not imply strict serializability. Weaker levels exist, and each store here names the one it targets as a compile-time isolation_k rather than leaving you to infer it.

Headers group as:

  basic_*               → Cores. Own their memory, no transactions, one thread.
    ├─ basic_vector<T, Alloc>
    ├─ basic_avl_tree<T, Comparator, Alloc>
    ├─ basic_wb_tree<T, Comparator, Alloc>          # also `rank` and `select`
    └─ basic_hash_table<T, Hash, Equals, Alloc>     # grows, iterates, rehashes

  atomic_*              → Pinned core. Fixed capacity, atomic per slot, callback reads.
    └─ atomic_hash_table<T, Hash, Equals, Alloc>    # the one type that runs on a GPU
       ↑ ::adopt(std::move(growable).release())     ↓ ::adopt(std::move(pinned).release())

  *_store               → 2-phase commit, watch and CAS.
    ├─ monotonic_store<Core>                        # one visible version per key
    ├─ snapshot_store<Core>                         # every version a live reader can still name
    └─ reference_store<T, Comparator, Alloc>        # the same contract over `std::set`, kept as the oracle
       ↓ either of the first two wraps a core; either of these wraps a store
    ├─ locked_store<Store, Mutex>                   # one lock spans a whole commit
    └─ partitioned_store<Store, Hash, Mutex, N>     # one lock per partition, one shared commit clock

  transaction_group<Stores...>  → One 2-phase commit spanning several stores

Transactions and thread-safety are two independent axes, not one ladder. A wrapper takes its mutex per call, not across a transaction: stage and commit each take it and drop it in between. What carries the inner store's level across that gap is the reservation staging made, since no other transaction can claim those keys until this one publishes or unwinds. A clock-less store gives its reader no stamp to hold, so a monotonic reader spanning partitions can catch a commit half-applied and only Read Committed survives — but a snapshot reader answers at its own stamp, and every partition draws from one clock, so it keeps Snapshot across all sixteen. Every container publishes what it promises as isolation_k, so the level is checkable rather than folklore. atomic_hash_table has no transactions at all — it offers per-operation atomicity, which is a different product, and is why it is not a *_store.

status_t reports out_of_memory_heap_k == ENOMEM, invalid_argument_k == EINVAL, key_not_found_k == ENOENT and others, and is [[nodiscard]] on every mutating API.

Making std::set Transactional

smashtable/reference_store.hpp

The baseline reference design, and the yardstick the tree containers are held against — it runs the same test suites they do. Updates group into transactions that stage and roll back before committing, which is what lets inter-dependent updates span several stores.

Reads are consistent at the Monotonic Atomic View level, so a transaction never sees another's partial update. That is weaker than Snapshot Isolation: there is no guarantee that every read within one transaction sees the same snapshot. It keeps a version per key all the same, dated by a generation rather than by a shared commit stamp, which is why a watch is re-resolved at commit instead of compared against one. snapshot_store is the sibling that does make that guarantee, and the section below states what it costs.

Adelson-Velsky and Landis Trees

smashtable/basic_avl_tree.hpp

Rarely called by its full name, the AVL tree is the simplest clean self-balancing binary search tree, from 1962. basic_avl_tree<value, comparator, allocator> is an ordered store in the shape of std::set or std::map. Standard libraries usually pick Red-Black trees; AVL balances more rigidly, trading slightly slower updates for faster lookups.

Its API differs from the STL where exception-free reporting demands it:

  • insert() returns std::pair<iterator, bool>, as the STL does.
  • erase(iterator) returns erase_result_t { iterator next; status_t status; }.
  • Range insert(first, last) returns status_t.
  • Hint-based insert(hint, value) and emplace_hint() are deleted, since AVL trees gain nothing from a hint.

Weight-Balanced Trees

smashtable/basic_wb_tree.hpp

basic_wb_tree balances on subtree sizes rather than heights, using Δ=3 and Γ=2 — the only proven integer solution, per Hirai and Yamamoto. Storing sizes buys order statistics: select(k) finds the k-th smallest and rank(x) finds a key's position, both in O(log n). That is what pagination, percentiles and quantiles need, and it is the one thing the AVL tree cannot offer.

Expect depth around 1.88 log₂(n) against AVL's 1.44, in exchange for O(1) amortized rotations per update.

Transactional Stores

smashtable/monotonic_store.hpp

monotonic_store<Core> adds two-phase commit, watches and CAS on top of any key-addressable core. monotonic_avl_set, monotonic_avl_map, monotonic_wb_set and monotonic_wb_map are the aliases you'll name directly. monotonic_hash_set and monotonic_hash_map back the same store with the open-addressed table, which supplies no ordering, so bounds, ranges and order statistics are gated out of those instantiations at compile time.

Which version a reader sees is decided by one number, stamped when a transaction commits. A version that has not committed carries no stamp, so it is invisible to readers and cannot make anyone else's validation fail — a transaction that stages and then rolls back costs its peers nothing. Two writers on one key are ordered by their stamps, and the later one wins. That is a lost update, which this level permits: Monotonic Atomic View constrains what a reader observes, not what two writers do to each other, and a level that is available under partition provably cannot prevent it. A writer that needs the first commit to win must watch() the key, or use a snapshot container, where the write set is validated whether you ask or not.

find never records what it read, here. A read set is memory, and a read that allocates is a read that can fail, so on this engine the recording variant is a different name:

_ = transaction.find(key, on_found, on_missing);             // answers, and records nothing
_ = transaction.find_and_watch(key, on_found, on_missing);   // records, and can report out of memory

Both return status_t, because a read that cannot write down what it read has to say so where it happened rather than poisoning the transaction silently until commit. snapshot_store at serializable_k and above needs no second name: every read records, and every read reports.

An erase leaves a tombstone that readers skip, and vacuum() is what returns that space:

expected<std::size_t> const reclaimed = store.vacuum();          // everything no reader can name
expected<std::size_t> const window = store.vacuum(lower, upper); // ordered cores, one slice at a time

Snapshot Isolation

smashtable/snapshot_store.hpp

snapshot_store<Core> is the sibling that fixes a transaction's reads to one instant, and it is where multi-version concurrency control is at its most literal here: a transaction draws a snapshot when it opens, every read is answered from the newest version at or below that snapshot, and a writer never overwrites what a reader can still name. It rebinds the same cores, exports snapshot_avl_set, _avl_map, _wb_set, _wb_map, _hash_set and _hash_map, and publishes isolation_k == snapshot_k. A repeated read returns what it first saw, a repeated range admits no phantoms, and neither holds in its sibling.

The same template answers at three levels, selected by a second parameter rather than by a second implementation, and each has aliases of its own:

snapshot_store<Core>                        // isolation_k == snapshot_k
serializable_store<Core>                    // reads validated too
strict_serializable_store<Core>             // and a commit waits for its own publication

serializable_avl_map<Key, Value>            // and `_avl_set`, `_wb_*`, `_hash_*`
strict_serializable_avl_map<Key, Value>     // the same six for the top rung

Versions are kept as ordinary entries keyed by (key, generation) rather than chained off one entry, on ordered and unordered cores alike. The hash core still hashes the bare key, so every version of a key shares one probe run and a lookup walks that run instead of stopping at the first match.

Reclamation is the other half of multi-version concurrency control, and the whole of it here: old versions are freed against a low-water mark that sits at the oldest open snapshot, so it can never pass a snapshot somebody still holds, and it advances as soon as the oldest reader leaves rather than waiting for the last. Pruning happens on every commit, and vacuum() reaches the keys nobody writes again. There is no background thread and no epoch registry. clear() refuses while a reader is open rather than dropping versions out from under it.

Range writes publish under one stamp, so a reader on an older snapshot sees all of a range erase or none of it. update_range builds new versions rather than rewriting the visible one, which is what open readers make unsound. select and rank descend on the weight-balanced tree's augmented count, exact at the newest commit — a reader at an older snapshot gets the merged walk instead, because one number per node cannot answer for an unbounded parameter.

What it costs, on an AVL core over mapping<key, int>:

monotonic_store snapshot_store
Resident per key, one version 80 B 72 B
Each retained older version 48 B chain node 72 B, a full entry
Read of one key one chain head the key's whole run

At rest it is the cheaper of the two, because a key carries no chain-head pointer and no per-entry allocator. It becomes the more expensive one exactly when a long-lived reader pins history — and on the hash core a pinned version occupies a real slot, so it lengthens the probe runs of its neighbours as well. With no transaction open the low-water mark sits at the newest commit and the whole version tail is freed on the next write, so a store nobody is reading costs what its sibling costs.

Hash Tables

smashtable/basic_hash_table.hpp

hash_set<key> and hash_map<key, value> are open-addressing containers with linear probing. They differ from google::dense_hash_map and tsl::robin_map by not reserving sentinel key values for tombstones, by unpacking keys and values into disjoint arrays, and by carrying two bits of metadata per slot rather than a byte.

Keys and values live in separate regions, so a lookup that only compares keys never pulls values into cache — which is most of them, since a miss touches no value at all. Thirty-two slots share one 64-bit header holding two parallel bitmasks, encoding free, deleted, populated and locked in two bits each. Sixty-four bits is the widest atomic every target supports, which is what lets a whole bucket header move in one instruction.

Growing repoints the key, value and header regions that every live slot reference has cached, so a table that can grow can never be read concurrently. That used to be a sentence here that nothing enforced. It is now two types, neither of which includes the other's header. basic_hash_table grows, iterates and rehashes; atomic_hash_table is pinned and atomic over the slot each operation touches. What passes between them is the allocation itself — a hash_storage — so the hand-off is a move of a value rather than one table reaching into the other:

_ = growable.reserve(1u << 20);                                          // pin the capacity first
auto pinned = atomic_hash_map<key_t, value_t>::adopt(std::move(growable).release());

_ = pinned.find(key, [](auto const &slot) noexcept { use(slot.value()); });
if (failed(pinned.emplace(key, value))) report_full();                   // a pinned table can fill up

auto compacted = hash_map<key_t, value_t>::adopt(std::move(pinned).release());   // iterators return

Every operation on the pinned table is atomic over the slot it touches: it takes that slot by setting both its bits with a fetch_or and releases it with a fetch_xor of the difference to the desired state, so neither path needs a compare-and-swap loop. It is not lock-free, though — fetch_or spins until it wins the slot, so a thread descheduled while holding one blocks every other prober that walks onto it. No global lock and no reallocation is what the pinning buys; a stalled thread stalling its neighbours is what it does not. Reads take a callback rather than returning a reference or an iterator, since both would dangle the moment another thread erased the slot. Tombstones only accumulate while pinned, because compaction needs a rehash — deleted_count() is what says it is time to hand the storage back.

Readers take the slot lock like writers do. A reader that merely loaded the header would be unsound, and measurably so: the two bits per slot cannot distinguish "untouched" from "locked, rewritten and unlocked", because a completed write cycle restores exactly the bits the reader first saw. Adding a per-bucket version counter would fix it and would cost half the slots per bucket, which is not worth breaking the one-warp-per-bucket geometry for.

Order-dependent operations are absent by construction: there is no range, erase_range, lower_bound or select.

The small-string specialization mentioned in the header is a design note, not shipped behaviour.

Testing

cmake -B build -DCMAKE_BUILD_TYPE=Debug -DSMASHTABLE_WERROR=ON
cmake --build build
ctest --test-dir build --output-on-failure

SMASHTABLE_FILTER selects a subset by substring, matched against suite.name:

SMASHTABLE_FILTER=transactional_consistency ./build/smashtable_test_avl_tree

One binary per container family runs the same suites — the std::set store, both trees, both thread-safety wrappers, the bare hash table and the transactional stores over it — so a behavioural difference between them shows up as a failure rather than a surprise.

The suite asserts costs, not only answers. A counting comparator bounds a descent: select over the augmented count visits 12 nodes at 4096 entries and 18 at 262144, and the same assertion fails at a tighter multiple, so the bound is not vacuous. An element type that tallies its own construction and destruction turns a leak into arithmetic — a key leaked inside a correctly freed node is invisible to a sanitizer and not to the tally. A key whose hash keeps only its group forces the long probe runs a well-spread hash never produces, which is where tombstone reuse and severed runs actually show.

isolation_k is checked against behaviour rather than against itself. The shared suite branches on the level a container declares and asserts both outcomes, so a container that quietly starts snapshotting without raising its level fails just as one that claims a level it does not deliver.

For the Python side:

pip install -e . --group test && pytest test/

Metadata

Release files for smashtable 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for smashtable 0.2.0
File
smashtable-0.2.0-cp314-cp314t-win_amd64.whl CPython 3.14 CPython 3.14 free-threading Windows x86-64 Details
smashtable-0.2.0-cp314-cp314t-win32.whl CPython 3.14 CPython 3.14 free-threading Windows x86-32 Details
smashtable-0.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl CPython 3.14 CPython 3.14 free-threading Linux musl 1.2+ x86-64 Details
smashtable-0.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl CPython 3.14 CPython 3.14 free-threading Linux musl 1.2+ ARM64 Details
smashtable-0.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 free-threading Linux glibc 2.28+ x86-64, Linux glibc 2.24+ x86-64 Details
smashtable-0.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.14 CPython 3.14 free-threading Linux glibc 2.24+ ARM64, Linux glibc 2.28+ ARM64 Details
smashtable-0.2.0-cp314-cp314t-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64 Details
smashtable-0.2.0-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
smashtable-0.2.0-cp314-cp314-win32.whl CPython 3.14 CPython 3.14 Windows x86-32 Details
smashtable-0.2.0-cp314-cp314-musllinux_1_2_x86_64.whl CPython 3.14 CPython 3.14 Linux musl 1.2+ x86-64 Details
smashtable-0.2.0-cp314-cp314-musllinux_1_2_aarch64.whl CPython 3.14 CPython 3.14 Linux musl 1.2+ ARM64 Details
smashtable-0.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details
smashtable-0.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ ARM64, Linux glibc 2.24+ ARM64 Details
smashtable-0.2.0-cp314-cp314-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 macOS 11.0+ ARM64 Details
smashtable-0.2.0-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
smashtable-0.2.0-cp313-cp313-win32.whl CPython 3.13 CPython 3.13 Windows x86-32 Details
smashtable-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl CPython 3.13 CPython 3.13 Linux musl 1.2+ x86-64 Details
smashtable-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl CPython 3.13 CPython 3.13 Linux musl 1.2+ ARM64 Details
smashtable-0.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details
smashtable-0.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ ARM64, Linux glibc 2.24+ ARM64 Details
smashtable-0.2.0-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details
smashtable-0.2.0-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
smashtable-0.2.0-cp312-cp312-win32.whl CPython 3.12 CPython 3.12 Windows x86-32 Details
smashtable-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl CPython 3.12 CPython 3.12 Linux musl 1.2+ x86-64 Details
smashtable-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl CPython 3.12 CPython 3.12 Linux musl 1.2+ ARM64 Details
smashtable-0.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details
smashtable-0.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.24+ ARM64, Linux glibc 2.28+ ARM64 Details
smashtable-0.2.0-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details

Total release size: 140.9 MB

Release files / smashtable-0.2.0-cp314-cp314t-win_amd64.whl

Download URL smashtable-0.2.0-cp314-cp314t-win_amd64.whl
Size 525.5 kB
Tags CPython 3.14 CPython 3.14 free-threading Windows x86-64
SHA-256 checksum
How to use checksums
bf87bb4c970c6013d90421c5b9fc4427a0ea9b1c46a614536672db5763d3601f
BLAKE2b-256 checksum
How to use checksums
1539cd83ab89e702291e8eb1bb0e83d1a3cf79d50d987818cbea397e9b39dd7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314t-win32.whl

Download URL smashtable-0.2.0-cp314-cp314t-win32.whl
Size 408.5 kB
Tags CPython 3.14 CPython 3.14 free-threading Windows x86-32
SHA-256 checksum
How to use checksums
cfb8b3d21b33ec465189736150dee5ce0509c1138c0804cec0d86685c9a1afd2
BLAKE2b-256 checksum
How to use checksums
183d00da0f0e9798f574318b3ba804a9bf7b60e7f1c98f9d24bcc576aee76e1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl

Download URL smashtable-0.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl
Size 8.9 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
6446e9030c5007d4aca667fb324235cc67f7d59d2654d46b4449621accb766aa
BLAKE2b-256 checksum
How to use checksums
0f95fb372433ea156323eb8deb2f938d214910218064e456cd71b7286f81a8ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl

Download URL smashtable-0.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl
Size 8.7 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
06425aaf1cd9082046fd479549d1f85533ef11af111683940589f1eebba06c72
BLAKE2b-256 checksum
How to use checksums
48e074990e68728dcc1aff190b02c4f44cf5eedf1b167afa0db66d35907e9427
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL smashtable-0.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 8.1 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
3e95f9d1dd3329e9dc1251da9410d45629c2e814c47f6a7ff3429dc42dc22920
BLAKE2b-256 checksum
How to use checksums
a8ff58aaba13a234a2362f92334f5508a37f937cf8adc33ddc1f63d3265d8d6f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL smashtable-0.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 8.1 MB
Tags CPython 3.14 CPython 3.14 free-threading Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
3cba27e0301d6f8801aba14fef40d1029cfec472208849a92566517ac3a5e1d5
BLAKE2b-256 checksum
How to use checksums
e039710db34169a9223321b9a1ca3a6c2c0250f17a0d85a713b9162b24115df7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314t-macosx_11_0_arm64.whl

Download URL smashtable-0.2.0-cp314-cp314t-macosx_11_0_arm64.whl
Size 668.8 kB
Tags CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
911628558c918206ee671c16e3f720b355e00dca52002e036bb8c9d2289d86f3
BLAKE2b-256 checksum
How to use checksums
a038cf74576b87178702cb66b69398d40aed98fdb899d92e728230d12a7899c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314-win_amd64.whl

Download URL smashtable-0.2.0-cp314-cp314-win_amd64.whl
Size 524.6 kB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
fae6bf29601abb74d50fbebebaedc4bb8fb933744bd077ebfa146e106cdbd0d0
BLAKE2b-256 checksum
How to use checksums
de5eaa99d99543d50e52d66d484ad5971902581a944473389513e5585a7748e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314-win32.whl

Download URL smashtable-0.2.0-cp314-cp314-win32.whl
Size 407.7 kB
Tags CPython 3.14 Windows x86-32
SHA-256 checksum
How to use checksums
59503192341d81bd4d66498327f69f7807429e29320fd9fc93417f17ebda436d
BLAKE2b-256 checksum
How to use checksums
b4f5e075c37a7cc9cc43b133a7858741a38dee4c143bcefe1c332c9d6ae7375f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314-musllinux_1_2_x86_64.whl

Download URL smashtable-0.2.0-cp314-cp314-musllinux_1_2_x86_64.whl
Size 8.8 MB
Tags CPython 3.14 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
9911d171bcccbaac549c7d4c8f602f6a16bef7078e67255d2f3411263d3f8488
BLAKE2b-256 checksum
How to use checksums
ec237249fcaa04624d29598874e0a47e1633f40359d7ddd17b0807862950bc0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314-musllinux_1_2_aarch64.whl

Download URL smashtable-0.2.0-cp314-cp314-musllinux_1_2_aarch64.whl
Size 8.7 MB
Tags CPython 3.14 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
f6bfbf47cfbcaf95da64da86443ad2e8631ba6c87d1da3e44dc386b150142746
BLAKE2b-256 checksum
How to use checksums
5687162e5d514c5b7e045c97fbee4e2aff4937b567da7f6916fea5f722ce17fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL smashtable-0.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 8.1 MB
Tags CPython 3.14 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
85adaf0e73fce6e1f93fc16e09fa558a2541d7031b4f74376649136ab7f6f07f
BLAKE2b-256 checksum
How to use checksums
115ffd4896df021889a9ce9962754a4287182c26bcf26f0e4926f5ed24744046
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL smashtable-0.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 8.0 MB
Tags CPython 3.14 Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
844b58192d2f785a9d2833004c616bd19ebd80437526b7496b639b95db400b5e
BLAKE2b-256 checksum
How to use checksums
156b63ebb3d09ddc072219383d552882bf4ea74169827d076487dcdc3f98b71a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp314-cp314-macosx_11_0_arm64.whl

Download URL smashtable-0.2.0-cp314-cp314-macosx_11_0_arm64.whl
Size 667.2 kB
Tags CPython 3.14 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1aa267487b146a3316240f141ea7d48958f4e3f25b8888dd9bf9bdcf5624b749
BLAKE2b-256 checksum
How to use checksums
8813f0aa3e42c193da68253b4234822b48eb95d776537df0112e2dca50d4c279
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp313-cp313-win_amd64.whl

Download URL smashtable-0.2.0-cp313-cp313-win_amd64.whl
Size 508.8 kB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
6bd0cddc19279e5b1f85db1ddfb3b75750db8b1e061544c59147b20d3cd6a619
BLAKE2b-256 checksum
How to use checksums
a2d08ba937ddbcf58ef1e4b961f7565e4224d3605c130ca5a3d5c31e34b9d4b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp313-cp313-win32.whl

Download URL smashtable-0.2.0-cp313-cp313-win32.whl
Size 397.4 kB
Tags CPython 3.13 Windows x86-32
SHA-256 checksum
How to use checksums
02218a3341bfa288e8483e53e9d0768ec012e0b5f4a2a77fad943a4e1396b628
BLAKE2b-256 checksum
How to use checksums
e9119884d549c19ce1a6f7b2cc61e0527da363774daa634fe30125dc4db915cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl

Download URL smashtable-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl
Size 8.8 MB
Tags CPython 3.13 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
6a21de90067da62d26d31eb1791676498e19b82a0d5ab5b1455cfd19169ccb88
BLAKE2b-256 checksum
How to use checksums
7c4548e221a64adfd844f7c01f3a0a3017c4213907d2b53cdbd83b72e123cc26
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl

Download URL smashtable-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl
Size 8.7 MB
Tags CPython 3.13 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
44c098533b4706bb720c6c4fdd70bf40c1b3d75adbdfffb0052a1a52636b2154
BLAKE2b-256 checksum
How to use checksums
00a01ed91c9f039aed5f1edff74da9ea93122c763b80aac1692ac88f2d4c9632
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL smashtable-0.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 8.1 MB
Tags CPython 3.13 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
e8dc497e76474339e624a37c3d45bdd7c1b966341b03385e13fe0d46bec0ba08
BLAKE2b-256 checksum
How to use checksums
899389e5d32b918179b93b1ef6e2060958934de3ad8dadabf95728449d6377c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL smashtable-0.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 8.0 MB
Tags CPython 3.13 Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
348aae216366ac415a87bc80804a150faf98aed7dc8ea563ef4a54a40b36dbd5
BLAKE2b-256 checksum
How to use checksums
ddd06224984908683643ae314f19ddfbce8e2b141118dfbaefd83562fdc965ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp313-cp313-macosx_11_0_arm64.whl

Download URL smashtable-0.2.0-cp313-cp313-macosx_11_0_arm64.whl
Size 667.1 kB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
0ea644ce14ce0f9a2564b714195a7757a8000e205aa7753f926f7ad93a84bab8
BLAKE2b-256 checksum
How to use checksums
f645d36d5fdd5030fbb2486153b3dab72d29ab5a99731fbedcf6b4948aba0036
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp312-cp312-win_amd64.whl

Download URL smashtable-0.2.0-cp312-cp312-win_amd64.whl
Size 508.9 kB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
b98c33f730a435562b33ad0b816135813a4941a82458c1115407d5c6255edf43
BLAKE2b-256 checksum
How to use checksums
c00bb364cb00bda48c90d0d4fbf118194a65af26080e0eec2526c1c5f392cde0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp312-cp312-win32.whl

Download URL smashtable-0.2.0-cp312-cp312-win32.whl
Size 397.4 kB
Tags CPython 3.12 Windows x86-32
SHA-256 checksum
How to use checksums
3b3d5cc62c49541a27d9fd6d8257969fb75e6b809f8fd21d05bdfb5b797db1f3
BLAKE2b-256 checksum
How to use checksums
250d540e4d0084f5478112dc1576c053b4377e5ad66bb95df0b1ffa3fc986030
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl

Download URL smashtable-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl
Size 8.8 MB
Tags CPython 3.12 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
72c44504d896a104ff2c734645b323baf8ee4e17dea9f6730623e95acdf7b775
BLAKE2b-256 checksum
How to use checksums
fe897796a56f38094cd88efdc55b965fd683dc23b42a23cab61865f316af63e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl

Download URL smashtable-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl
Size 8.7 MB
Tags CPython 3.12 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
b2864ea18e1c4aa41412c6038d95374e8344e282218e767bc34c2c3bc7f3a5bd
BLAKE2b-256 checksum
How to use checksums
e82969468af302c671278b223c04af4ae755f433a9a85529fe80942f01db1a55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL smashtable-0.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 8.1 MB
Tags CPython 3.12 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
eafb5a521dfe46db10e45d84b404fd82bbbba86eb923e9ef89afe93ad269c397
BLAKE2b-256 checksum
How to use checksums
5422258071f05a366be6b086e360b1fe30b42690eaac4828e1e8587bd3a4606f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL smashtable-0.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 8.0 MB
Tags CPython 3.12 Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
667e964f5926bd16edef6068047656ae6bf98b36e502be3d68ac4aa0585dcbec
BLAKE2b-256 checksum
How to use checksums
fe783b208bcb89c4ff8004077a9422538a2b5ac66241cab6ab4e5b3b28379b5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / smashtable-0.2.0-cp312-cp312-macosx_11_0_arm64.whl

Download URL smashtable-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
Size 667.1 kB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
83b1661655244e9fb6e2f120f95ae82f4c90f2244be388840b33faacb62f97ca
BLAKE2b-256 checksum
How to use checksums
1e1ea4966e2b0b914b43c975cd395ef6aacac6680352c9bc1bf881774349131e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.1

32 release files

0.3.0

28 release files

This release

0.2.0 This release

28 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page