41template <
typename Element>
59 explicit SequenceTrie(uint32_t numShards = 1, uint32_t chunkSizeLog2 = 20)
60 : _chunkBits(chunkSizeLog2), _chunkSize(1u << chunkSizeLog2), _chunkMask((1u << chunkSizeLog2) - 1), _shards(numShards == 0 ? 1 : numShards)
62 for (
auto& c : _chunks) c.store(
nullptr, std::memory_order_relaxed);
63 for (
auto& s : _shards) s.head =
NONE;
64 _bump.store(0, std::memory_order_relaxed);
70 r.element = Element{};
71 r.refCount.store(1, std::memory_order_relaxed);
76 for (
auto& c : _chunks)
78 Node* p = c.load(std::memory_order_relaxed);
83 SequenceTrie(
const SequenceTrie&) =
delete;
84 SequenceTrie& operator=(
const SequenceTrie&) =
delete;
97 const nodeId_t id = allocNode(shard);
101 n.refCount.store(1, std::memory_order_relaxed);
104 incRef(node(parent));
128 if (n.refCount.fetch_sub(1, std::memory_order_acq_rel) != 1)
break;
141 template <
typename OutElem = Element>
147 const Node& n = node(cur);
148 out.push_back(
static_cast<OutElem
>(n.element));
151 std::reverse(out.begin(), out.end());
162 for (
nodeId_t cur =
id; cur !=
ROOT && cur !=
NONE; cur = node(cur).parent) d++;
193 std::atomic<uint16_t> refCount;
199 static constexpr size_t MAX_CHUNKS = 4096;
201 __attribute__((always_inline)) Node& node(
nodeId_t id)
const {
return _chunks[
id >> _chunkBits].load(std::memory_order_acquire)[
id & _chunkMask]; }
207 __attribute__((always_inline))
void incRef(Node& n)
const
209 if (n.refCount.fetch_add(1, std::memory_order_relaxed) == UINT16_MAX)
210 JAFFAR_THROW_RUNTIME(
"SequenceTrie node refCount overflowed 16 bits (out-degree + holders > %u); the "
211 "input set is too large for the 16-bit-refCount trie node.",
212 (
unsigned)UINT16_MAX);
221 FreeShard& fs = _shards[shard];
225 fs.head = node(top).parent;
230 const nodeId_t id = (
nodeId_t)_bump.fetch_add(1, std::memory_order_relaxed);
231 const size_t chunk =
id >> _chunkBits;
232 if (chunk >= MAX_CHUNKS)
JAFFAR_THROW_RUNTIME(
"SequenceTrie exceeded its maximum capacity (%zu chunks)", MAX_CHUNKS);
233 if (_chunks[chunk].load(std::memory_order_acquire) ==
nullptr) ensureChunk(chunk);
237 void freeNode(
nodeId_t id, uint32_t shard)
239 FreeShard& fs = _shards[shard];
240 node(
id).parent = fs.head;
244 void ensureChunk(
size_t chunk)
246 std::lock_guard<std::mutex> lock(_growMutex);
247 if (_chunks[chunk].load(std::memory_order_relaxed) !=
nullptr)
return;
248 Node* p =
new Node[_chunkSize];
249 _chunks[chunk].store(p, std::memory_order_release);
254 struct alignas(64) FreeShard
259 const uint32_t _chunkBits;
260 const uint32_t _chunkSize;
261 const uint32_t _chunkMask;
263 mutable std::array<std::atomic<Node*>, MAX_CHUNKS> _chunks;
264 std::atomic<uint64_t> _bump;
265 std::vector<FreeShard> _shards;
266 std::mutex _growMutex;
A concurrent reference-counted prefix trie over sequences of Element.
Definition sequenceTrie.hpp:43
size_t getAllocatedNodeCount() const
Total node slots ever bump-allocated (high-water of simultaneously-live nodes, since freed nodes are ...
Definition sequenceTrie.hpp:171
void reconstruct(nodeId_t id, std::vector< OutElem > &out) const
Reconstructs the full sequence from ROOT to id, in root-first order.
Definition sequenceTrie.hpp:142
size_t getMaxMemoryBytes() const
Hard upper bound on the trie's node storage, in bytes: the most it can ever occupy before node alloca...
Definition sequenceTrie.hpp:186
uint32_t nodeId_t
Handle identifying a node (a stored sequence). Stable for the node's lifetime.
Definition sequenceTrie.hpp:46
nodeId_t extend(nodeId_t parent, Element element, uint32_t shard=0)
Appends element after parent, returning a handle to the new sequence.
Definition sequenceTrie.hpp:95
static constexpr nodeId_t NONE
Sentinel for "no node" (also the free-list terminator).
Definition sequenceTrie.hpp:52
size_t getDepth(nodeId_t id) const
Number of elements from ROOT to id (its depth in the trie).
Definition sequenceTrie.hpp:159
size_t getApproxMemoryBytes() const
Approximate resident memory of the trie's node storage, in bytes.
Definition sequenceTrie.hpp:177
static constexpr nodeId_t ROOT
The empty sequence. Always valid, never recycled; the base of every path.
Definition sequenceTrie.hpp:49
SequenceTrie(uint32_t numShards=1, uint32_t chunkSizeLog2=20)
Constructs an empty trie containing only ROOT.
Definition sequenceTrie.hpp:59
void acquire(nodeId_t id)
Adds one reference to id (when a new holder starts referencing it). Thread-safe.
Definition sequenceTrie.hpp:112
void release(nodeId_t id, uint32_t shard=0)
Drops one reference from id. Thread-safe.
Definition sequenceTrie.hpp:122
Contains common functions for exception throwing.
#define JAFFAR_THROW_RUNTIME(...)
Definition exceptions.hpp:22