Open addressed hash containers, hand rolled for the Nim VM.
Everything Necsus does with a hash table happens while compiling, and std/tables and std/sets are surprisingly expensive there. Every lookup goes through the generic hashcommon.rawGet machinery, and std/hashes runs Wang Yi's mixer over each word of each key. Between them they account for a few million VM instructions when building the archetype graph for a large app.
These are deliberately plainer:
- Entries live in flat, parallel seqs. The bucket array holds nothing but integers, so probing only reaches for a key once a hash code has already matched.
- A bucket holds one plus an index into those seqs, which lets zero mean "empty" without having to prefill anything.
- There is no del. Nothing here needs it, and leaving it out means a probe can stop at the first empty bucket rather than walking past tombstones.
Insertion order is preserved, so iteration is stable across compiles -- which matters when the output feeds code generation.
As with std/tables, a bucket is picked from the low bits of hash, and probing is linear. A key type whose hash leaves its low bits poorly mixed will cluster, and that shows up as a slow compile rather than as a wrong answer.
Procs
proc containsOrIncl[K](openSet: var OpenSet[K]; key: K): bool
- Adds key and returns whether it was already there. This is the primitive worth reaching for -- a contains followed by an incl probes twice
proc getOrDefault[K, V](table: OpenTable[K, V]; key: K): V
- The value stored against key, or a default value when it is missing
proc initOpenSet[K](expected: int = 32): OpenSet[K]
- Creates a set sized to hold expected entries without rehashing
proc initOpenTable[K, V](expected: int = 32): OpenTable[K, V]
- Creates a table sized to hold expected entries without rehashing