// Package flatset provides flat open-addressing sets and maps for the // dedup/membership/grouping structures that dominate allocation-heavy // inner loops. Go's built-in map allocates a bucket array per doubling // plus overflow buckets as it fills, and map[K]struct{} insert forces // per-key work the flat probe tables avoid: every structure here costs // exactly one slice allocation per doubling. Shared by the gql executor's // aggregation path and the native benchmark kernels.
package flatset // U32Set is an open-addressing set of uint32 ids. Slots store id+1 so // zero marks empty -- an id of ^uint32(0) cannot occur for array-indexed // ids (node ids, CSR positions). type U32Set struct { slots []uint32 mask uint32 count int // Rec, when set, recycles slot arrays across the sets sharing it: // grow returns the outgrown array to the pool and draws replacements // from it. Many same-shaped sets climbing the growth ladder together // (one DISTINCT set per aggregation group) then churn roughly one // array per rung instead of one per set per rung -- the discarded // doublings otherwise dominate the aggregation's allocation profile. Rec *Recycle } // Recycle is a slot-array free list keyed by size class, shared by many // U32Sets within ONE single-threaded execution (no locking). Arrays are // zeroed on reuse (the same memclr a fresh make pays), so the saving is // purely the allocation and its GC/scavenger shadow. type Recycle struct { byLog [26][][]uint32 } // take returns a zeroed array of exactly n slots (n a power of two), or // nil when the class is empty. func ( *Recycle) ( int) []uint32 { if == nil { return nil } := logOf() := .byLog[] if len() == 0 { return nil } := [len()-1] .byLog[] = [:len()-1] clear() return } // put files an outgrown array under its size class. func ( *Recycle) ( []uint32) { if == nil || len() == 0 { return } := logOf(len()) .byLog[] = append(.byLog[], ) } // logOf is log2 for the power-of-two slot sizes, clamped into byLog. func logOf( int) int { := 0 for > 1 { >>= 1 ++ } if >= 26 { = 25 } return } // Add reports whether id is newly seen (and records it). func ( *U32Set) ( uint32) bool { if .slots == nil { if .slots = .Rec.take(16); .slots == nil { .slots = make([]uint32, 16) } .mask = 15 } if .count*4 >= len(.slots)*3 { .grow() } := + 1 := ( * 2654435761) & .mask for .slots[] != 0 { if .slots[] == { return false } = ( + 1) & .mask } .slots[] = .count++ return true } // Has reports whether id was added. func ( *U32Set) ( uint32) bool { if .slots == nil { return false } := + 1 := ( * 2654435761) & .mask for .slots[] != 0 { if .slots[] == { return true } = ( + 1) & .mask } return false } // Len is the number of distinct ids added. func ( *U32Set) () int { return .count } // Reset empties the set keeping its slot backing, so a reused set's next // fill allocates nothing until it outgrows its high-water size. func ( *U32Set) () { clear(.slots) .count = 0 } // Built reports whether the probe table has been materialized (any Add // ran) -- callers layering their own inline fast path ahead of the set // key their spill decision on it. func ( *U32Set) () bool { return .slots != nil } func ( *U32Set) () { := .slots if .slots = .Rec.take(len() * 2); .slots == nil { .slots = make([]uint32, len()*2) } .mask = uint32(len(.slots) - 1) for , := range { if == 0 { continue } := (( - 1) * 2654435761) & .mask for .slots[] != 0 { = ( + 1) & .mask } .slots[] = } .Rec.put() } // u64Hash is the Fibonacci multiplicative hash over the full key. func u64Hash( uint64) uint32 { return uint32( * 0x9E3779B97F4A7C15 >> 32) } // U64Set is an open-addressing set of uint64 keys -- the packed-pair // membership set (e.g. person<<32|forum). Slots store key+1 so zero marks // empty; the all-ones key cannot occur for packed array-indexed ids. type U64Set struct { slots []uint64 mask uint32 count int } // Add reports whether key is newly seen (and records it). func ( *U64Set) ( uint64) bool { if .slots == nil { .slots = make([]uint64, 16) .mask = 15 } if .count*4 >= len(.slots)*3 { .grow() } := + 1 := u64Hash() & .mask for .slots[] != 0 { if .slots[] == { return false } = ( + 1) & .mask } .slots[] = .count++ return true } // Has reports whether key was added. func ( *U64Set) ( uint64) bool { if .slots == nil { return false } := + 1 := u64Hash() & .mask for .slots[] != 0 { if .slots[] == { return true } = ( + 1) & .mask } return false } // Len is the number of distinct keys added. func ( *U64Set) () int { return .count } // Reset empties the set keeping its slot backing, so a reused set's next // fill allocates nothing until it outgrows its high-water size. func ( *U64Set) () { clear(.slots) .count = 0 } func ( *U64Set) () { := .slots .slots = make([]uint64, len()*2) .mask = uint32(len(.slots) - 1) for , := range { if == 0 { continue } := u64Hash(-1) & .mask for .slots[] != 0 { = ( + 1) & .mask } .slots[] = } } // U64Map maps uint64 keys to an int index through an open-addressing // probe table. Slots carry an explicit used flag, so every key value is // valid. type U64Map struct { slots []u64Slot mask uint32 count int } // u64Slot is one open-addressing slot. type u64Slot struct { key uint64 val int32 used bool } // GetOrCreate returns the index mapped to key, calling create to mint it // on first sight (create must not touch this map). func ( *U64Map) ( uint64, func() int) int { if .slots == nil { .slots = make([]u64Slot, 16) .mask = 15 } if .count*4 >= len(.slots)*3 { .grow() } := u64Hash() & .mask for .slots[].used { if .slots[].key == { return int(.slots[].val) } = ( + 1) & .mask } := () .slots[] = u64Slot{key: , val: int32(), used: true} .count++ return } // Get returns the index mapped to key; ok is false when absent. func ( *U64Map) ( uint64) (int, bool) { if .slots == nil { return 0, false } := u64Hash() & .mask for .slots[].used { if .slots[].key == { return int(.slots[].val), true } = ( + 1) & .mask } return 0, false } // ForEach visits every (key, index) pair in table order. The visit // function must not mutate the map. func ( *U64Map) ( func( uint64, int)) { for , := range .slots { if .used { (.key, int(.val)) } } } // Len is the number of distinct keys mapped. func ( *U64Map) () int { return .count } // Reset empties the map keeping its table backing, so a reused map's // next fill allocates nothing until it outgrows its high-water size. func ( *U64Map) () { clear(.slots) .count = 0 } func ( *U64Map) () { := .slots .slots = make([]u64Slot, len()*2) .mask = uint32(len(.slots) - 1) for , := range { if !.used { continue } := u64Hash(.key) & .mask for .slots[].used { = ( + 1) & .mask } .slots[] = } }