github.com/moontrade/nogc@v0.1.7/pointer_set.go (about) 1 package nogc 2 3 import ( 4 "github.com/moontrade/nogc/hash" 5 "unsafe" 6 ) 7 8 // PointerSet is a hashset that uses the robinhood algorithm. This 9 // implementation is not concurrent safe. 10 type PointerSet struct { 11 // items are the slots of the hashmap for items. 12 items uintptr 13 end uintptr 14 size uintptr 15 16 // Number of keys in the PointerSet. 17 count uintptr 18 // When any item's distance gets too large, Grow the PointerSet. 19 // Defaults to 10. 20 maxDistance int32 21 growBy int32 22 // Number of hash slots to Grow by 23 growthFactor float32 24 } 25 26 // Item represents an entry in the PointerSet. 27 type pointerSetItem struct { 28 key uintptr 29 distance int32 // How far item is from its best position. 30 } 31 32 // pointerSetHash uses the fnv hashing algorithm for 32bit integers. 33 // fnv was chosen for its consistent low collision rate even with tight monotonic numbers (WASM) 34 // and for its performance. Other potential candidates are wyhash, metro, and adler32. Each of these 35 // have optimized 32bit version in hash.go in this package. 36 //go:inline 37 var pointerSetHash = hash.FNV32a 38 39 //func pointerSetHash(v uint32) uint32 { 40 // return fnv32(v) 41 //} 42 43 // NewPointerSet returns a new robinhood hashmap. 44 //goland:noinspection ALL 45 func NewPointerSet(size uintptr) PointerSet { 46 items := AllocZeroed(unsafe.Sizeof(pointerSetItem{}) * size) 47 Zero(unsafe.Pointer(items), unsafe.Sizeof(pointerSetItem{})*uintptr(size)) 48 return PointerSet{ 49 items: uintptr(items), 50 size: uintptr(size), 51 end: uintptr(items) + (uintptr(size) * unsafe.Sizeof(pointerSetItem{})), 52 maxDistance: 10, 53 growBy: 64, 54 growthFactor: 2.0, 55 } 56 } 57 58 //goland:noinspection GoVetUnsafePointer 59 func (ps *PointerSet) Close() error { 60 if ps == nil { 61 return nil 62 } 63 if ps.items == 0 { 64 return nil 65 } 66 Free(Pointer(ps.items)) 67 ps.items = 0 68 return nil 69 } 70 71 // Reset clears PointerSet, where already allocated memory will be reused. 72 //goland:noinspection ALL 73 func (ps *PointerSet) Reset() { 74 Zero(unsafe.Pointer(ps.items), unsafe.Sizeof(pointerSetItem{})*ps.size) 75 ps.count = 0 76 } 77 78 //goland:noinspection GoVetUnsafePointer 79 func (ps *PointerSet) isCollision(key uintptr) bool { 80 return *(*uintptr)(unsafe.Pointer( 81 ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{})))) != 0 82 } 83 84 // Has returns whether the key exists in the Add. 85 //goland:noinspection ALL 86 func (ps *PointerSet) Has(key uintptr) bool { 87 var ( 88 idx = ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{})) 89 idxStart = idx 90 ) 91 for { 92 entry := *(*uintptr)(unsafe.Pointer(idx)) 93 if entry == 0 { 94 return false 95 } 96 if entry == key { 97 return true 98 } 99 idx += unsafe.Sizeof(pointerSetItem{}) 100 if idx >= ps.end { 101 idx = ps.items 102 } 103 // Went all the way around? 104 if idx == idxStart { 105 return false 106 } 107 } 108 } 109 110 func (ps *PointerSet) Set(key uintptr) (bool, bool) { 111 return ps.Add(key, 0) 112 } 113 114 // Add inserts or updates a key into the PointerSet. The returned 115 // wasNew will be true if the mutation was on a newly seen, inserted 116 // key, and wasNew will be false if the mutation was an update to an 117 // existing key. 118 //goland:noinspection GoVetUnsafePointer 119 func (ps *PointerSet) Add(key uintptr, depth int) (bool, bool) { 120 var ( 121 idx = ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{})) 122 idxStart = idx 123 incoming = pointerSetItem{key, 0} 124 ) 125 for { 126 entry := (*pointerSetItem)(unsafe.Pointer(idx)) 127 if entry.key == 0 { 128 *(*pointerSetItem)(unsafe.Pointer(idx)) = incoming 129 ps.count++ 130 return true, true 131 } 132 133 if entry.key == incoming.key { 134 entry.distance = incoming.distance 135 return false, true 136 } 137 138 // Swap if the incoming item is further from its best idx. 139 if entry.distance < incoming.distance { 140 incoming, *(*pointerSetItem)(unsafe.Pointer(idx)) = *(*pointerSetItem)(unsafe.Pointer(idx)), incoming 141 } 142 143 // One step further away from best idx. 144 incoming.distance++ 145 146 idx += unsafe.Sizeof(pointerSetItem{}) 147 if idx >= ps.end { 148 idx = ps.items 149 } 150 151 // Grow if distances become big or we went all the way around. 152 if incoming.distance > ps.maxDistance || idx == idxStart { 153 if depth > 5 { 154 return false, false 155 } 156 if !ps.Grow() { 157 return false, false 158 } 159 return ps.Add(incoming.key, depth+1) 160 } 161 } 162 } 163 164 // Delete removes a key from the PointerSet. 165 //goland:noinspection GoVetUnsafePointer 166 func (ps *PointerSet) Delete(key uintptr) (uintptr, bool) { 167 if key == 0 { 168 return 0, false 169 } 170 171 var ( 172 idx = ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{})) 173 idxStart = idx 174 ) 175 for { 176 entry := (*pointerSetItem)(unsafe.Pointer(idx)) 177 if entry.key == 0 { 178 return 0, false 179 } 180 if entry.key == key { 181 break // Found the item. 182 } 183 idx += unsafe.Sizeof(pointerSetItem{}) 184 if idx >= ps.end { 185 idx = ps.items 186 } 187 if idx == idxStart { 188 return 0, false 189 } 190 } 191 // Left-shift succeeding items in the linear chain. 192 for { 193 next := idx + unsafe.Sizeof(pointerSetItem{}) 194 if next >= ps.end { 195 next = ps.items 196 } 197 // Went all the way around? 198 if next == idx { 199 break 200 } 201 f := (*pointerSetItem)(unsafe.Pointer(next)) 202 if f.key == 0 || f.distance <= 0 { 203 break 204 } 205 f.distance-- 206 *(*pointerSetItem)(unsafe.Pointer(idx)) = *f 207 idx = next 208 } 209 // Clear entry 210 *(*pointerSetItem)(unsafe.Pointer(idx)) = pointerSetItem{} 211 ps.count-- 212 return idxStart, true 213 } 214 215 //goland:noinspection GoVetUnsafePointer 216 func (ps *PointerSet) Grow() bool { 217 // Calculate new size 218 // ps.size + 128 219 if ps.growthFactor <= 1.0 { 220 ps.growthFactor = 2.0 221 } 222 //newSize := ps.size + 32 // uintptr(float32(ps.size) * ps.growthFactor) 223 newSize := uintptr(float32(ps.size) * ps.growthFactor) 224 225 //if gc_TRACE { 226 // println("PointerSet.Grow", "newSize", uint(newSize), "oldSize", uint(ps.size)) 227 //} 228 229 // Allocate new items table 230 items := uintptr(AllocZeroed(newSize * unsafe.Sizeof(pointerSetItem{}))) 231 // Calculate end 232 itemsEnd := items + (newSize * unsafe.Sizeof(pointerSetItem{})) 233 // Init next structure 234 next := PointerSet{ 235 items: items, 236 size: newSize, 237 end: itemsEnd, 238 count: 0, 239 growthFactor: ps.growthFactor, 240 maxDistance: ps.maxDistance, 241 } 242 243 // Add all entries from old to next 244 var success bool 245 for i := ps.items; i < ps.end; i += unsafe.Sizeof(pointerSetItem{}) { 246 if _, success = next.Add(*(*uintptr)(unsafe.Pointer(i)), 0); !success { 247 return false 248 } 249 } 250 251 // Free old items 252 Free(Pointer(ps.items)) 253 ps.items = 0 254 255 // Update to next 256 *ps = next 257 return true 258 }