gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

hashmap.go (9483B)


      1 // Package hashmap provides a lock-free and thread-safe HashMap.
      2 package hashmap
      3 
      4 import (
      5 	"bytes"
      6 	"fmt"
      7 	"reflect"
      8 	"strconv"
      9 	"sync/atomic"
     10 	"unsafe"
     11 )
     12 
     13 // Map implements a read optimized hash map.
     14 type Map[Key hashable, Value any] struct {
     15 	hasher     func(Key) uintptr
     16 	store      atomic.Pointer[store[Key, Value]] // pointer to a map instance that gets replaced if the map resizes
     17 	linkedList *List[Key, Value]                 // key sorted linked list of elements
     18 	// resizing marks a resizing operation in progress.
     19 	// this is using uintptr instead of atomic.Bool to avoid using 32 bit int on 64 bit systems
     20 	resizing atomic.Uintptr
     21 }
     22 
     23 // New returns a new map instance.
     24 func New[Key hashable, Value any]() *Map[Key, Value] {
     25 	return NewSized[Key, Value](defaultSize)
     26 }
     27 
     28 // NewSized returns a new map instance with a specific initialization size.
     29 func NewSized[Key hashable, Value any](size uintptr) *Map[Key, Value] {
     30 	m := &Map[Key, Value]{}
     31 	m.allocate(size)
     32 	m.setDefaultHasher()
     33 	return m
     34 }
     35 
     36 // SetHasher sets a custom hasher.
     37 func (m *Map[Key, Value]) SetHasher(hasher func(Key) uintptr) {
     38 	m.hasher = hasher
     39 }
     40 
     41 // Len returns the number of elements within the map.
     42 func (m *Map[Key, Value]) Len() int {
     43 	return m.linkedList.Len()
     44 }
     45 
     46 // Get retrieves an element from the map under given hash key.
     47 func (m *Map[Key, Value]) Get(key Key) (Value, bool) {
     48 	hash := m.hasher(key)
     49 
     50 	for element := m.store.Load().item(hash); element != nil; element = element.Next() {
     51 		if element.keyHash == hash && element.key == key {
     52 			return element.Value(), true
     53 		}
     54 
     55 		if element.keyHash > hash {
     56 			return *new(Value), false
     57 		}
     58 	}
     59 	return *new(Value), false
     60 }
     61 
     62 // GetOrInsert returns the existing value for the key if present.
     63 // Otherwise, it stores and returns the given value.
     64 // The returned bool is true if the value was loaded, false if stored.
     65 func (m *Map[Key, Value]) GetOrInsert(key Key, value Value) (Value, bool) {
     66 	hash := m.hasher(key)
     67 	var newElement *ListElement[Key, Value]
     68 
     69 	for {
     70 		for element := m.store.Load().item(hash); element != nil; element = element.Next() {
     71 			if element.keyHash == hash && element.key == key {
     72 				actual := element.Value()
     73 				return actual, true
     74 			}
     75 
     76 			if element.keyHash > hash {
     77 				break
     78 			}
     79 		}
     80 
     81 		if newElement == nil { // allocate only once
     82 			newElement = &ListElement[Key, Value]{
     83 				key:     key,
     84 				keyHash: hash,
     85 			}
     86 			newElement.value.Store(&value)
     87 		}
     88 
     89 		if m.insertElement(newElement, hash, key, value) {
     90 			return value, false
     91 		}
     92 	}
     93 }
     94 
     95 // FillRate returns the fill rate of the map as a percentage integer.
     96 func (m *Map[Key, Value]) FillRate() int {
     97 	store := m.store.Load()
     98 	count := int(store.count.Load())
     99 	l := len(store.index)
    100 	return (count * 100) / l
    101 }
    102 
    103 // Del deletes the key from the map and returns whether the key was deleted.
    104 func (m *Map[Key, Value]) Del(key Key) bool {
    105 	hash := m.hasher(key)
    106 	store := m.store.Load()
    107 	element := store.item(hash)
    108 
    109 	for ; element != nil; element = element.Next() {
    110 		if element.keyHash == hash && element.key == key {
    111 			m.deleteElement(element)
    112 			m.linkedList.Delete(element)
    113 			return true
    114 		}
    115 
    116 		if element.keyHash > hash {
    117 			return false
    118 		}
    119 	}
    120 	return false
    121 }
    122 
    123 // Insert sets the value under the specified key to the map if it does not exist yet.
    124 // If a resizing operation is happening concurrently while calling Insert, the item might show up in the map
    125 // after the resize operation is finished.
    126 // Returns true if the item was inserted or false if it existed.
    127 func (m *Map[Key, Value]) Insert(key Key, value Value) bool {
    128 	hash := m.hasher(key)
    129 	var (
    130 		existed, inserted bool
    131 		element           *ListElement[Key, Value]
    132 	)
    133 
    134 	for {
    135 		store := m.store.Load()
    136 		searchStart := store.item(hash)
    137 
    138 		if !inserted { // if retrying after insert during grow, do not add to list again
    139 			element, existed, inserted = m.linkedList.Add(searchStart, hash, key, value)
    140 			if existed {
    141 				return false
    142 			}
    143 			if !inserted {
    144 				continue // a concurrent add did interfere, try again
    145 			}
    146 		}
    147 
    148 		count := store.addItem(element)
    149 		currentStore := m.store.Load()
    150 		if store != currentStore { // retry insert in case of insert during grow
    151 			continue
    152 		}
    153 
    154 		if m.isResizeNeeded(store, count) && m.resizing.CompareAndSwap(0, 1) {
    155 			go m.grow(0, true)
    156 		}
    157 		return true
    158 	}
    159 }
    160 
    161 // Set sets the value under the specified key to the map. An existing item for this key will be overwritten.
    162 // If a resizing operation is happening concurrently while calling Set, the item might show up in the map
    163 // after the resize operation is finished.
    164 func (m *Map[Key, Value]) Set(key Key, value Value) {
    165 	hash := m.hasher(key)
    166 
    167 	for {
    168 		store := m.store.Load()
    169 		searchStart := store.item(hash)
    170 
    171 		element, added := m.linkedList.AddOrUpdate(searchStart, hash, key, value)
    172 		if !added {
    173 			continue // a concurrent add did interfere, try again
    174 		}
    175 
    176 		count := store.addItem(element)
    177 		currentStore := m.store.Load()
    178 		if store != currentStore { // retry insert in case of insert during grow
    179 			continue
    180 		}
    181 
    182 		if m.isResizeNeeded(store, count) && m.resizing.CompareAndSwap(0, 1) {
    183 			go m.grow(0, true)
    184 		}
    185 		return
    186 	}
    187 }
    188 
    189 // Grow resizes the map to a new size, the size gets rounded up to next power of 2.
    190 // To double the size of the map use newSize 0.
    191 // This function returns immediately, the resize operation is done in a goroutine.
    192 // No resizing is done in case of another resize operation already being in progress.
    193 func (m *Map[Key, Value]) Grow(newSize uintptr) {
    194 	if m.resizing.CompareAndSwap(0, 1) {
    195 		go m.grow(newSize, true)
    196 	}
    197 }
    198 
    199 // String returns the map as a string, only hashed keys are printed.
    200 func (m *Map[Key, Value]) String() string {
    201 	buffer := bytes.NewBufferString("")
    202 	buffer.WriteRune('[')
    203 
    204 	first := m.linkedList.First()
    205 	item := first
    206 
    207 	for item != nil {
    208 		if item != first {
    209 			buffer.WriteRune(',')
    210 		}
    211 		fmt.Fprint(buffer, item.keyHash)
    212 		item = item.Next()
    213 	}
    214 	buffer.WriteRune(']')
    215 	return buffer.String()
    216 }
    217 
    218 // Range calls f sequentially for each key and value present in the map.
    219 // If f returns false, range stops the iteration.
    220 func (m *Map[Key, Value]) Range(f func(Key, Value) bool) {
    221 	item := m.linkedList.First()
    222 
    223 	for item != nil {
    224 		value := item.Value()
    225 		if !f(item.key, value) {
    226 			return
    227 		}
    228 		item = item.Next()
    229 	}
    230 }
    231 
    232 func (m *Map[Key, Value]) allocate(newSize uintptr) {
    233 	m.linkedList = NewList[Key, Value]()
    234 	if m.resizing.CompareAndSwap(0, 1) {
    235 		m.grow(newSize, false)
    236 	}
    237 }
    238 
    239 func (m *Map[Key, Value]) isResizeNeeded(store *store[Key, Value], count uintptr) bool {
    240 	l := uintptr(len(store.index)) // l can't be 0 as it gets initialized in New()
    241 	fillRate := (count * 100) / l
    242 	return fillRate > maxFillRate
    243 }
    244 
    245 func (m *Map[Key, Value]) insertElement(element *ListElement[Key, Value], hash uintptr, key Key, value Value) bool {
    246 	var existed, inserted bool
    247 
    248 	for {
    249 		store := m.store.Load()
    250 		searchStart := store.item(element.keyHash)
    251 
    252 		if !inserted { // if retrying after insert during grow, do not add to list again
    253 			_, existed, inserted = m.linkedList.Add(searchStart, hash, key, value)
    254 			if existed {
    255 				return false
    256 			}
    257 
    258 			if !inserted {
    259 				continue // a concurrent add did interfere, try again
    260 			}
    261 		}
    262 
    263 		count := store.addItem(element)
    264 		currentStore := m.store.Load()
    265 		if store != currentStore { // retry insert in case of insert during grow
    266 			continue
    267 		}
    268 
    269 		if m.isResizeNeeded(store, count) && m.resizing.CompareAndSwap(0, 1) {
    270 			go m.grow(0, true)
    271 		}
    272 		return true
    273 	}
    274 }
    275 
    276 // deleteElement deletes an element from index.
    277 func (m *Map[Key, Value]) deleteElement(element *ListElement[Key, Value]) {
    278 	for {
    279 		store := m.store.Load()
    280 		index := element.keyHash >> store.keyShifts
    281 		ptr := (*unsafe.Pointer)(unsafe.Pointer(uintptr(store.array) + index*intSizeBytes))
    282 
    283 		next := element.Next()
    284 		if next != nil && element.keyHash>>store.keyShifts != index {
    285 			next = nil // do not set index to next item if it's not the same slice index
    286 		}
    287 		atomic.CompareAndSwapPointer(ptr, unsafe.Pointer(element), unsafe.Pointer(next))
    288 
    289 		currentStore := m.store.Load()
    290 		if store == currentStore { // check that no resize happened
    291 			break
    292 		}
    293 	}
    294 }
    295 
    296 func (m *Map[Key, Value]) grow(newSize uintptr, loop bool) {
    297 	defer m.resizing.CompareAndSwap(1, 0)
    298 
    299 	for {
    300 		currentStore := m.store.Load()
    301 		if newSize == 0 {
    302 			newSize = uintptr(len(currentStore.index)) << 1
    303 		} else {
    304 			newSize = roundUpPower2(newSize)
    305 		}
    306 
    307 		index := make([]*ListElement[Key, Value], newSize)
    308 		header := (*reflect.SliceHeader)(unsafe.Pointer(&index))
    309 
    310 		newStore := &store[Key, Value]{
    311 			keyShifts: strconv.IntSize - log2(newSize),
    312 			array:     unsafe.Pointer(header.Data), // use address of slice data storage
    313 			index:     index,
    314 		}
    315 
    316 		m.fillIndexItems(newStore) // initialize new index slice with longer keys
    317 
    318 		m.store.Store(newStore)
    319 
    320 		m.fillIndexItems(newStore) // make sure that the new index is up-to-date with the current state of the linked list
    321 
    322 		if !loop {
    323 			return
    324 		}
    325 
    326 		// check if a new resize needs to be done already
    327 		count := uintptr(m.Len())
    328 		if !m.isResizeNeeded(newStore, count) {
    329 			return
    330 		}
    331 		newSize = 0 // 0 means double the current size
    332 	}
    333 }
    334 
    335 func (m *Map[Key, Value]) fillIndexItems(store *store[Key, Value]) {
    336 	first := m.linkedList.First()
    337 	item := first
    338 	lastIndex := uintptr(0)
    339 
    340 	for item != nil {
    341 		index := item.keyHash >> store.keyShifts
    342 		if item == first || index != lastIndex { // store item with smallest hash key for every index
    343 			store.addItem(item)
    344 			lastIndex = index
    345 		}
    346 		item = item.Next()
    347 	}
    348 }