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