diff options
author | 2022-11-05 12:10:19 +0100 | |
---|---|---|
committer | 2022-11-05 11:10:19 +0000 | |
commit | bcb80d3ff4a669d52d63950c8830427646c05884 (patch) | |
tree | 4aa95a83545b3f87a80fe4b625cb6f2ad9c4427f /vendor/github.com/cornelk/hashmap/hashmap.go | |
parent | [bugfix] Increase field size limits when registering apps (#958) (diff) | |
download | gotosocial-bcb80d3ff4a669d52d63950c8830427646c05884.tar.xz |
[chore] bump gruf/go-store to v2 (#953)
* [chore] bump gruf/go-store to v2
* no more boobs
Diffstat (limited to 'vendor/github.com/cornelk/hashmap/hashmap.go')
-rw-r--r-- | vendor/github.com/cornelk/hashmap/hashmap.go | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/vendor/github.com/cornelk/hashmap/hashmap.go b/vendor/github.com/cornelk/hashmap/hashmap.go new file mode 100644 index 000000000..dbceb52b7 --- /dev/null +++ b/vendor/github.com/cornelk/hashmap/hashmap.go @@ -0,0 +1,348 @@ +// Package hashmap provides a lock-free and thread-safe HashMap. +package hashmap + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "sync/atomic" + "unsafe" +) + +// Map implements a read optimized hash map. +type Map[Key hashable, Value any] struct { + hasher func(Key) uintptr + store atomic.Pointer[store[Key, Value]] // pointer to a map instance that gets replaced if the map resizes + linkedList *List[Key, Value] // key sorted linked list of elements + // resizing marks a resizing operation in progress. + // this is using uintptr instead of atomic.Bool to avoid using 32 bit int on 64 bit systems + resizing atomic.Uintptr +} + +// New returns a new map instance. +func New[Key hashable, Value any]() *Map[Key, Value] { + return NewSized[Key, Value](defaultSize) +} + +// NewSized returns a new map instance with a specific initialization size. +func NewSized[Key hashable, Value any](size uintptr) *Map[Key, Value] { + m := &Map[Key, Value]{} + m.allocate(size) + m.setDefaultHasher() + return m +} + +// SetHasher sets a custom hasher. +func (m *Map[Key, Value]) SetHasher(hasher func(Key) uintptr) { + m.hasher = hasher +} + +// Len returns the number of elements within the map. +func (m *Map[Key, Value]) Len() int { + return m.linkedList.Len() +} + +// Get retrieves an element from the map under given hash key. +func (m *Map[Key, Value]) Get(key Key) (Value, bool) { + hash := m.hasher(key) + + for element := m.store.Load().item(hash); element != nil; element = element.Next() { + if element.keyHash == hash && element.key == key { + return element.Value(), true + } + + if element.keyHash > hash { + return *new(Value), false + } + } + return *new(Value), false +} + +// GetOrInsert returns the existing value for the key if present. +// Otherwise, it stores and returns the given value. +// The returned bool is true if the value was loaded, false if stored. +func (m *Map[Key, Value]) GetOrInsert(key Key, value Value) (Value, bool) { + hash := m.hasher(key) + var newElement *ListElement[Key, Value] + + for { + for element := m.store.Load().item(hash); element != nil; element = element.Next() { + if element.keyHash == hash && element.key == key { + actual := element.Value() + return actual, true + } + + if element.keyHash > hash { + break + } + } + + if newElement == nil { // allocate only once + newElement = &ListElement[Key, Value]{ + key: key, + keyHash: hash, + } + newElement.value.Store(&value) + } + + if m.insertElement(newElement, hash, key, value) { + return value, false + } + } +} + +// FillRate returns the fill rate of the map as a percentage integer. +func (m *Map[Key, Value]) FillRate() int { + store := m.store.Load() + count := int(store.count.Load()) + l := len(store.index) + return (count * 100) / l +} + +// Del deletes the key from the map and returns whether the key was deleted. +func (m *Map[Key, Value]) Del(key Key) bool { + hash := m.hasher(key) + store := m.store.Load() + element := store.item(hash) + + for ; element != nil; element = element.Next() { + if element.keyHash == hash && element.key == key { + m.deleteElement(element) + m.linkedList.Delete(element) + return true + } + + if element.keyHash > hash { + return false + } + } + return false +} + +// Insert sets the value under the specified key to the map if it does not exist yet. +// If a resizing operation is happening concurrently while calling Insert, the item might show up in the map +// after the resize operation is finished. +// Returns true if the item was inserted or false if it existed. +func (m *Map[Key, Value]) Insert(key Key, value Value) bool { + hash := m.hasher(key) + var ( + existed, inserted bool + element *ListElement[Key, Value] + ) + + for { + store := m.store.Load() + searchStart := store.item(hash) + + if !inserted { // if retrying after insert during grow, do not add to list again + element, existed, inserted = m.linkedList.Add(searchStart, hash, key, value) + if existed { + return false + } + if !inserted { + continue // a concurrent add did interfere, try again + } + } + + count := store.addItem(element) + currentStore := m.store.Load() + if store != currentStore { // retry insert in case of insert during grow + continue + } + + if m.isResizeNeeded(store, count) && m.resizing.CompareAndSwap(0, 1) { + go m.grow(0, true) + } + return true + } +} + +// Set sets the value under the specified key to the map. An existing item for this key will be overwritten. +// If a resizing operation is happening concurrently while calling Set, the item might show up in the map +// after the resize operation is finished. +func (m *Map[Key, Value]) Set(key Key, value Value) { + hash := m.hasher(key) + + for { + store := m.store.Load() + searchStart := store.item(hash) + + element, added := m.linkedList.AddOrUpdate(searchStart, hash, key, value) + if !added { + continue // a concurrent add did interfere, try again + } + + count := store.addItem(element) + currentStore := m.store.Load() + if store != currentStore { // retry insert in case of insert during grow + continue + } + + if m.isResizeNeeded(store, count) && m.resizing.CompareAndSwap(0, 1) { + go m.grow(0, true) + } + return + } +} + +// Grow resizes the map to a new size, the size gets rounded up to next power of 2. +// To double the size of the map use newSize 0. +// This function returns immediately, the resize operation is done in a goroutine. +// No resizing is done in case of another resize operation already being in progress. +func (m *Map[Key, Value]) Grow(newSize uintptr) { + if m.resizing.CompareAndSwap(0, 1) { + go m.grow(newSize, true) + } +} + +// String returns the map as a string, only hashed keys are printed. +func (m *Map[Key, Value]) String() string { + buffer := bytes.NewBufferString("") + buffer.WriteRune('[') + + first := m.linkedList.First() + item := first + + for item != nil { + if item != first { + buffer.WriteRune(',') + } + fmt.Fprint(buffer, item.keyHash) + item = item.Next() + } + buffer.WriteRune(']') + return buffer.String() +} + +// Range calls f sequentially for each key and value present in the map. +// If f returns false, range stops the iteration. +func (m *Map[Key, Value]) Range(f func(Key, Value) bool) { + item := m.linkedList.First() + + for item != nil { + value := item.Value() + if !f(item.key, value) { + return + } + item = item.Next() + } +} + +func (m *Map[Key, Value]) allocate(newSize uintptr) { + m.linkedList = NewList[Key, Value]() + if m.resizing.CompareAndSwap(0, 1) { + m.grow(newSize, false) + } +} + +func (m *Map[Key, Value]) isResizeNeeded(store *store[Key, Value], count uintptr) bool { + l := uintptr(len(store.index)) // l can't be 0 as it gets initialized in New() + fillRate := (count * 100) / l + return fillRate > maxFillRate +} + +func (m *Map[Key, Value]) insertElement(element *ListElement[Key, Value], hash uintptr, key Key, value Value) bool { + var existed, inserted bool + + for { + store := m.store.Load() + searchStart := store.item(element.keyHash) + + if !inserted { // if retrying after insert during grow, do not add to list again + _, existed, inserted = m.linkedList.Add(searchStart, hash, key, value) + if existed { + return false + } + + if !inserted { + continue // a concurrent add did interfere, try again + } + } + + count := store.addItem(element) + currentStore := m.store.Load() + if store != currentStore { // retry insert in case of insert during grow + continue + } + + if m.isResizeNeeded(store, count) && m.resizing.CompareAndSwap(0, 1) { + go m.grow(0, true) + } + return true + } +} + +// deleteElement deletes an element from index. +func (m *Map[Key, Value]) deleteElement(element *ListElement[Key, Value]) { + for { + store := m.store.Load() + index := element.keyHash >> store.keyShifts + ptr := (*unsafe.Pointer)(unsafe.Pointer(uintptr(store.array) + index*intSizeBytes)) + + next := element.Next() + if next != nil && element.keyHash>>store.keyShifts != index { + next = nil // do not set index to next item if it's not the same slice index + } + atomic.CompareAndSwapPointer(ptr, unsafe.Pointer(element), unsafe.Pointer(next)) + + currentStore := m.store.Load() + if store == currentStore { // check that no resize happened + break + } + } +} + +func (m *Map[Key, Value]) grow(newSize uintptr, loop bool) { + defer m.resizing.CompareAndSwap(1, 0) + + for { + currentStore := m.store.Load() + if newSize == 0 { + newSize = uintptr(len(currentStore.index)) << 1 + } else { + newSize = roundUpPower2(newSize) + } + + index := make([]*ListElement[Key, Value], newSize) + header := (*reflect.SliceHeader)(unsafe.Pointer(&index)) + + newStore := &store[Key, Value]{ + keyShifts: strconv.IntSize - log2(newSize), + array: unsafe.Pointer(header.Data), // use address of slice data storage + index: index, + } + + m.fillIndexItems(newStore) // initialize new index slice with longer keys + + m.store.Store(newStore) + + m.fillIndexItems(newStore) // make sure that the new index is up-to-date with the current state of the linked list + + if !loop { + return + } + + // check if a new resize needs to be done already + count := uintptr(m.Len()) + if !m.isResizeNeeded(newStore, count) { + return + } + newSize = 0 // 0 means double the current size + } +} + +func (m *Map[Key, Value]) fillIndexItems(store *store[Key, Value]) { + first := m.linkedList.First() + item := first + lastIndex := uintptr(0) + + for item != nil { + index := item.keyHash >> store.keyShifts + if item == first || index != lastIndex { // store item with smallest hash key for every index + store.addItem(item) + lastIndex = index + } + item = item.Next() + } +} |