mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-01 17:42:25 -05:00
[chore] update bun libraries to v1.2.5 (#3528)
* update bun libraries to v1.2.5 * pin old v1.29.0 of otel
This commit is contained in:
parent
45e1609377
commit
29007b1b88
59 changed files with 4181 additions and 1196 deletions
694
vendor/github.com/puzpuzpuz/xsync/v3/mapof.go
generated
vendored
Normal file
694
vendor/github.com/puzpuzpuz/xsync/v3/mapof.go
generated
vendored
Normal file
|
|
@ -0,0 +1,694 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// number of MapOf entries per bucket; 5 entries lead to size of 64B
|
||||
// (one cache line) on 64-bit machines
|
||||
entriesPerMapOfBucket = 5
|
||||
defaultMeta uint64 = 0x8080808080808080
|
||||
metaMask uint64 = 0xffffffffff
|
||||
defaultMetaMasked uint64 = defaultMeta & metaMask
|
||||
emptyMetaSlot uint8 = 0x80
|
||||
)
|
||||
|
||||
// MapOf is like a Go map[K]V but is safe for concurrent
|
||||
// use by multiple goroutines without additional locking or
|
||||
// coordination. It follows the interface of sync.Map with
|
||||
// a number of valuable extensions like Compute or Size.
|
||||
//
|
||||
// A MapOf must not be copied after first use.
|
||||
//
|
||||
// MapOf uses a modified version of Cache-Line Hash Table (CLHT)
|
||||
// data structure: https://github.com/LPD-EPFL/CLHT
|
||||
//
|
||||
// CLHT is built around idea to organize the hash table in
|
||||
// cache-line-sized buckets, so that on all modern CPUs update
|
||||
// operations complete with at most one cache-line transfer.
|
||||
// Also, Get operations involve no write to memory, as well as no
|
||||
// mutexes or any other sort of locks. Due to this design, in all
|
||||
// considered scenarios MapOf outperforms sync.Map.
|
||||
//
|
||||
// MapOf also borrows ideas from Java's j.u.c.ConcurrentHashMap
|
||||
// (immutable K/V pair structs instead of atomic snapshots)
|
||||
// and C++'s absl::flat_hash_map (meta memory and SWAR-based
|
||||
// lookups).
|
||||
type MapOf[K comparable, V any] struct {
|
||||
totalGrowths int64
|
||||
totalShrinks int64
|
||||
resizing int64 // resize in progress flag; updated atomically
|
||||
resizeMu sync.Mutex // only used along with resizeCond
|
||||
resizeCond sync.Cond // used to wake up resize waiters (concurrent modifications)
|
||||
table unsafe.Pointer // *mapOfTable
|
||||
hasher func(K, uint64) uint64
|
||||
minTableLen int
|
||||
growOnly bool
|
||||
}
|
||||
|
||||
type mapOfTable[K comparable, V any] struct {
|
||||
buckets []bucketOfPadded
|
||||
// striped counter for number of table entries;
|
||||
// used to determine if a table shrinking is needed
|
||||
// occupies min(buckets_memory/1024, 64KB) of memory
|
||||
size []counterStripe
|
||||
seed uint64
|
||||
}
|
||||
|
||||
// bucketOfPadded is a CL-sized map bucket holding up to
|
||||
// entriesPerMapOfBucket entries.
|
||||
type bucketOfPadded struct {
|
||||
//lint:ignore U1000 ensure each bucket takes two cache lines on both 32 and 64-bit archs
|
||||
pad [cacheLineSize - unsafe.Sizeof(bucketOf{})]byte
|
||||
bucketOf
|
||||
}
|
||||
|
||||
type bucketOf struct {
|
||||
meta uint64
|
||||
entries [entriesPerMapOfBucket]unsafe.Pointer // *entryOf
|
||||
next unsafe.Pointer // *bucketOfPadded
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// entryOf is an immutable map entry.
|
||||
type entryOf[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
// NewMapOf creates a new MapOf instance configured with the given
|
||||
// options.
|
||||
func NewMapOf[K comparable, V any](options ...func(*MapConfig)) *MapOf[K, V] {
|
||||
return NewMapOfWithHasher[K, V](defaultHasher[K](), options...)
|
||||
}
|
||||
|
||||
// NewMapOfWithHasher creates a new MapOf instance configured with
|
||||
// the given hasher and options. The hash function is used instead
|
||||
// of the built-in hash function configured when a map is created
|
||||
// with the NewMapOf function.
|
||||
func NewMapOfWithHasher[K comparable, V any](
|
||||
hasher func(K, uint64) uint64,
|
||||
options ...func(*MapConfig),
|
||||
) *MapOf[K, V] {
|
||||
c := &MapConfig{
|
||||
sizeHint: defaultMinMapTableLen * entriesPerMapOfBucket,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(c)
|
||||
}
|
||||
|
||||
m := &MapOf[K, V]{}
|
||||
m.resizeCond = *sync.NewCond(&m.resizeMu)
|
||||
m.hasher = hasher
|
||||
var table *mapOfTable[K, V]
|
||||
if c.sizeHint <= defaultMinMapTableLen*entriesPerMapOfBucket {
|
||||
table = newMapOfTable[K, V](defaultMinMapTableLen)
|
||||
} else {
|
||||
tableLen := nextPowOf2(uint32((float64(c.sizeHint) / entriesPerMapOfBucket) / mapLoadFactor))
|
||||
table = newMapOfTable[K, V](int(tableLen))
|
||||
}
|
||||
m.minTableLen = len(table.buckets)
|
||||
m.growOnly = c.growOnly
|
||||
atomic.StorePointer(&m.table, unsafe.Pointer(table))
|
||||
return m
|
||||
}
|
||||
|
||||
// NewMapOfPresized creates a new MapOf instance with capacity enough
|
||||
// to hold sizeHint entries. The capacity is treated as the minimal capacity
|
||||
// meaning that the underlying hash table will never shrink to
|
||||
// a smaller capacity. If sizeHint is zero or negative, the value
|
||||
// is ignored.
|
||||
//
|
||||
// Deprecated: use NewMapOf in combination with WithPresize.
|
||||
func NewMapOfPresized[K comparable, V any](sizeHint int) *MapOf[K, V] {
|
||||
return NewMapOf[K, V](WithPresize(sizeHint))
|
||||
}
|
||||
|
||||
func newMapOfTable[K comparable, V any](minTableLen int) *mapOfTable[K, V] {
|
||||
buckets := make([]bucketOfPadded, minTableLen)
|
||||
for i := range buckets {
|
||||
buckets[i].meta = defaultMeta
|
||||
}
|
||||
counterLen := minTableLen >> 10
|
||||
if counterLen < minMapCounterLen {
|
||||
counterLen = minMapCounterLen
|
||||
} else if counterLen > maxMapCounterLen {
|
||||
counterLen = maxMapCounterLen
|
||||
}
|
||||
counter := make([]counterStripe, counterLen)
|
||||
t := &mapOfTable[K, V]{
|
||||
buckets: buckets,
|
||||
size: counter,
|
||||
seed: makeSeed(),
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or zero value
|
||||
// of type V if no value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
func (m *MapOf[K, V]) Load(key K) (value V, ok bool) {
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
hash := m.hasher(key, table.seed)
|
||||
h1 := h1(hash)
|
||||
h2w := broadcast(h2(hash))
|
||||
bidx := uint64(len(table.buckets)-1) & h1
|
||||
b := &table.buckets[bidx]
|
||||
for {
|
||||
metaw := atomic.LoadUint64(&b.meta)
|
||||
markedw := markZeroBytes(metaw^h2w) & metaMask
|
||||
for markedw != 0 {
|
||||
idx := firstMarkedByteIndex(markedw)
|
||||
eptr := atomic.LoadPointer(&b.entries[idx])
|
||||
if eptr != nil {
|
||||
e := (*entryOf[K, V])(eptr)
|
||||
if e.key == key {
|
||||
return e.value, true
|
||||
}
|
||||
}
|
||||
markedw &= markedw - 1
|
||||
}
|
||||
bptr := atomic.LoadPointer(&b.next)
|
||||
if bptr == nil {
|
||||
return
|
||||
}
|
||||
b = (*bucketOfPadded)(bptr)
|
||||
}
|
||||
}
|
||||
|
||||
// Store sets the value for a key.
|
||||
func (m *MapOf[K, V]) Store(key K, value V) {
|
||||
m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return value, false
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrStore returns the existing value for the key if present.
|
||||
// Otherwise, it stores and returns the given value.
|
||||
// The loaded result is true if the value was loaded, false if stored.
|
||||
func (m *MapOf[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return value, false
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadAndStore returns the existing value for the key if present,
|
||||
// while setting the new value for the key.
|
||||
// It stores the new value and returns the existing one, if present.
|
||||
// The loaded result is true if the existing value was loaded,
|
||||
// false otherwise.
|
||||
func (m *MapOf[K, V]) LoadAndStore(key K, value V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return value, false
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrCompute returns the existing value for the key if present.
|
||||
// Otherwise, it computes the value using the provided function and
|
||||
// returns the computed value. The loaded result is true if the value
|
||||
// was loaded, false if stored.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *MapOf[K, V]) LoadOrCompute(key K, valueFn func() V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return valueFn(), false
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Compute either sets the computed new value for the key or deletes
|
||||
// the value for the key. When the delete result of the valueFn function
|
||||
// is set to true, the value will be deleted, if it exists. When delete
|
||||
// is set to false, the value is updated to the newValue.
|
||||
// The ok result indicates whether value was computed and stored, thus, is
|
||||
// present in the map. The actual result contains the new value in cases where
|
||||
// the value was computed and stored. See the example for a few use cases.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *MapOf[K, V]) Compute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (newValue V, delete bool),
|
||||
) (actual V, ok bool) {
|
||||
return m.doCompute(key, valueFn, false, true)
|
||||
}
|
||||
|
||||
// LoadAndDelete deletes the value for a key, returning the previous
|
||||
// value if any. The loaded result reports whether the key was
|
||||
// present.
|
||||
func (m *MapOf[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(value V, loaded bool) (V, bool) {
|
||||
return value, true
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Delete deletes the value for a key.
|
||||
func (m *MapOf[K, V]) Delete(key K) {
|
||||
m.doCompute(
|
||||
key,
|
||||
func(value V, loaded bool) (V, bool) {
|
||||
return value, true
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) doCompute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (V, bool),
|
||||
loadIfExists, computeOnly bool,
|
||||
) (V, bool) {
|
||||
// Read-only path.
|
||||
if loadIfExists {
|
||||
if v, ok := m.Load(key); ok {
|
||||
return v, !computeOnly
|
||||
}
|
||||
}
|
||||
// Write path.
|
||||
for {
|
||||
compute_attempt:
|
||||
var (
|
||||
emptyb *bucketOfPadded
|
||||
emptyidx int
|
||||
)
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
tableLen := len(table.buckets)
|
||||
hash := m.hasher(key, table.seed)
|
||||
h1 := h1(hash)
|
||||
h2 := h2(hash)
|
||||
h2w := broadcast(h2)
|
||||
bidx := uint64(len(table.buckets)-1) & h1
|
||||
rootb := &table.buckets[bidx]
|
||||
rootb.mu.Lock()
|
||||
// The following two checks must go in reverse to what's
|
||||
// in the resize method.
|
||||
if m.resizeInProgress() {
|
||||
// Resize is in progress. Wait, then go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
m.waitForResize()
|
||||
goto compute_attempt
|
||||
}
|
||||
if m.newerTableExists(table) {
|
||||
// Someone resized the table. Go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
goto compute_attempt
|
||||
}
|
||||
b := rootb
|
||||
for {
|
||||
metaw := b.meta
|
||||
markedw := markZeroBytes(metaw^h2w) & metaMask
|
||||
for markedw != 0 {
|
||||
idx := firstMarkedByteIndex(markedw)
|
||||
eptr := b.entries[idx]
|
||||
if eptr != nil {
|
||||
e := (*entryOf[K, V])(eptr)
|
||||
if e.key == key {
|
||||
if loadIfExists {
|
||||
rootb.mu.Unlock()
|
||||
return e.value, !computeOnly
|
||||
}
|
||||
// In-place update/delete.
|
||||
// We get a copy of the value via an interface{} on each call,
|
||||
// thus the live value pointers are unique. Otherwise atomic
|
||||
// snapshot won't be correct in case of multiple Store calls
|
||||
// using the same value.
|
||||
oldv := e.value
|
||||
newv, del := valueFn(oldv, true)
|
||||
if del {
|
||||
// Deletion.
|
||||
// First we update the hash, then the entry.
|
||||
newmetaw := setByte(metaw, emptyMetaSlot, idx)
|
||||
atomic.StoreUint64(&b.meta, newmetaw)
|
||||
atomic.StorePointer(&b.entries[idx], nil)
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, -1)
|
||||
// Might need to shrink the table if we left bucket empty.
|
||||
if newmetaw == defaultMeta {
|
||||
m.resize(table, mapShrinkHint)
|
||||
}
|
||||
return oldv, !computeOnly
|
||||
}
|
||||
newe := new(entryOf[K, V])
|
||||
newe.key = key
|
||||
newe.value = newv
|
||||
atomic.StorePointer(&b.entries[idx], unsafe.Pointer(newe))
|
||||
rootb.mu.Unlock()
|
||||
if computeOnly {
|
||||
// Compute expects the new value to be returned.
|
||||
return newv, true
|
||||
}
|
||||
// LoadAndStore expects the old value to be returned.
|
||||
return oldv, true
|
||||
}
|
||||
}
|
||||
markedw &= markedw - 1
|
||||
}
|
||||
if emptyb == nil {
|
||||
// Search for empty entries (up to 5 per bucket).
|
||||
emptyw := metaw & defaultMetaMasked
|
||||
if emptyw != 0 {
|
||||
idx := firstMarkedByteIndex(emptyw)
|
||||
emptyb = b
|
||||
emptyidx = idx
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
if emptyb != nil {
|
||||
// Insertion into an existing bucket.
|
||||
var zeroedV V
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
if del {
|
||||
rootb.mu.Unlock()
|
||||
return zeroedV, false
|
||||
}
|
||||
newe := new(entryOf[K, V])
|
||||
newe.key = key
|
||||
newe.value = newValue
|
||||
// First we update meta, then the entry.
|
||||
atomic.StoreUint64(&emptyb.meta, setByte(emptyb.meta, h2, emptyidx))
|
||||
atomic.StorePointer(&emptyb.entries[emptyidx], unsafe.Pointer(newe))
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
growThreshold := float64(tableLen) * entriesPerMapOfBucket * mapLoadFactor
|
||||
if table.sumSize() > int64(growThreshold) {
|
||||
// Need to grow the table. Then go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
m.resize(table, mapGrowHint)
|
||||
goto compute_attempt
|
||||
}
|
||||
// Insertion into a new bucket.
|
||||
var zeroedV V
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
if del {
|
||||
rootb.mu.Unlock()
|
||||
return newValue, false
|
||||
}
|
||||
// Create and append a bucket.
|
||||
newb := new(bucketOfPadded)
|
||||
newb.meta = setByte(defaultMeta, h2, 0)
|
||||
newe := new(entryOf[K, V])
|
||||
newe.key = key
|
||||
newe.value = newValue
|
||||
newb.entries[0] = unsafe.Pointer(newe)
|
||||
atomic.StorePointer(&b.next, unsafe.Pointer(newb))
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) newerTableExists(table *mapOfTable[K, V]) bool {
|
||||
curTablePtr := atomic.LoadPointer(&m.table)
|
||||
return uintptr(curTablePtr) != uintptr(unsafe.Pointer(table))
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) resizeInProgress() bool {
|
||||
return atomic.LoadInt64(&m.resizing) == 1
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) waitForResize() {
|
||||
m.resizeMu.Lock()
|
||||
for m.resizeInProgress() {
|
||||
m.resizeCond.Wait()
|
||||
}
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) resize(knownTable *mapOfTable[K, V], hint mapResizeHint) {
|
||||
knownTableLen := len(knownTable.buckets)
|
||||
// Fast path for shrink attempts.
|
||||
if hint == mapShrinkHint {
|
||||
if m.growOnly ||
|
||||
m.minTableLen == knownTableLen ||
|
||||
knownTable.sumSize() > int64((knownTableLen*entriesPerMapOfBucket)/mapShrinkFraction) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Slow path.
|
||||
if !atomic.CompareAndSwapInt64(&m.resizing, 0, 1) {
|
||||
// Someone else started resize. Wait for it to finish.
|
||||
m.waitForResize()
|
||||
return
|
||||
}
|
||||
var newTable *mapOfTable[K, V]
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
tableLen := len(table.buckets)
|
||||
switch hint {
|
||||
case mapGrowHint:
|
||||
// Grow the table with factor of 2.
|
||||
atomic.AddInt64(&m.totalGrowths, 1)
|
||||
newTable = newMapOfTable[K, V](tableLen << 1)
|
||||
case mapShrinkHint:
|
||||
shrinkThreshold := int64((tableLen * entriesPerMapOfBucket) / mapShrinkFraction)
|
||||
if tableLen > m.minTableLen && table.sumSize() <= shrinkThreshold {
|
||||
// Shrink the table with factor of 2.
|
||||
atomic.AddInt64(&m.totalShrinks, 1)
|
||||
newTable = newMapOfTable[K, V](tableLen >> 1)
|
||||
} else {
|
||||
// No need to shrink. Wake up all waiters and give up.
|
||||
m.resizeMu.Lock()
|
||||
atomic.StoreInt64(&m.resizing, 0)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
return
|
||||
}
|
||||
case mapClearHint:
|
||||
newTable = newMapOfTable[K, V](m.minTableLen)
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected resize hint: %d", hint))
|
||||
}
|
||||
// Copy the data only if we're not clearing the map.
|
||||
if hint != mapClearHint {
|
||||
for i := 0; i < tableLen; i++ {
|
||||
copied := copyBucketOf(&table.buckets[i], newTable, m.hasher)
|
||||
newTable.addSizePlain(uint64(i), copied)
|
||||
}
|
||||
}
|
||||
// Publish the new table and wake up all waiters.
|
||||
atomic.StorePointer(&m.table, unsafe.Pointer(newTable))
|
||||
m.resizeMu.Lock()
|
||||
atomic.StoreInt64(&m.resizing, 0)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func copyBucketOf[K comparable, V any](
|
||||
b *bucketOfPadded,
|
||||
destTable *mapOfTable[K, V],
|
||||
hasher func(K, uint64) uint64,
|
||||
) (copied int) {
|
||||
rootb := b
|
||||
rootb.mu.Lock()
|
||||
for {
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if b.entries[i] != nil {
|
||||
e := (*entryOf[K, V])(b.entries[i])
|
||||
hash := hasher(e.key, destTable.seed)
|
||||
bidx := uint64(len(destTable.buckets)-1) & h1(hash)
|
||||
destb := &destTable.buckets[bidx]
|
||||
appendToBucketOf(h2(hash), b.entries[i], destb)
|
||||
copied++
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
rootb.mu.Unlock()
|
||||
return
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls f sequentially for each key and value present in the
|
||||
// map. If f returns false, range stops the iteration.
|
||||
//
|
||||
// Range does not necessarily correspond to any consistent snapshot
|
||||
// of the Map's contents: no key will be visited more than once, but
|
||||
// if the value for any key is stored or deleted concurrently, Range
|
||||
// may reflect any mapping for that key from any point during the
|
||||
// Range call.
|
||||
//
|
||||
// It is safe to modify the map while iterating it, including entry
|
||||
// creation, modification and deletion. However, the concurrent
|
||||
// modification rule apply, i.e. the changes may be not reflected
|
||||
// in the subsequently iterated entries.
|
||||
func (m *MapOf[K, V]) Range(f func(key K, value V) bool) {
|
||||
var zeroPtr unsafe.Pointer
|
||||
// Pre-allocate array big enough to fit entries for most hash tables.
|
||||
bentries := make([]unsafe.Pointer, 0, 16*entriesPerMapOfBucket)
|
||||
tablep := atomic.LoadPointer(&m.table)
|
||||
table := *(*mapOfTable[K, V])(tablep)
|
||||
for i := range table.buckets {
|
||||
rootb := &table.buckets[i]
|
||||
b := rootb
|
||||
// Prevent concurrent modifications and copy all entries into
|
||||
// the intermediate slice.
|
||||
rootb.mu.Lock()
|
||||
for {
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if b.entries[i] != nil {
|
||||
bentries = append(bentries, b.entries[i])
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
rootb.mu.Unlock()
|
||||
break
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
// Call the function for all copied entries.
|
||||
for j := range bentries {
|
||||
entry := (*entryOf[K, V])(bentries[j])
|
||||
if !f(entry.key, entry.value) {
|
||||
return
|
||||
}
|
||||
// Remove the reference to avoid preventing the copied
|
||||
// entries from being GCed until this method finishes.
|
||||
bentries[j] = zeroPtr
|
||||
}
|
||||
bentries = bentries[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Clear deletes all keys and values currently stored in the map.
|
||||
func (m *MapOf[K, V]) Clear() {
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
m.resize(table, mapClearHint)
|
||||
}
|
||||
|
||||
// Size returns current size of the map.
|
||||
func (m *MapOf[K, V]) Size() int {
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
return int(table.sumSize())
|
||||
}
|
||||
|
||||
func appendToBucketOf(h2 uint8, entryPtr unsafe.Pointer, b *bucketOfPadded) {
|
||||
for {
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if b.entries[i] == nil {
|
||||
b.meta = setByte(b.meta, h2, i)
|
||||
b.entries[i] = entryPtr
|
||||
return
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
newb := new(bucketOfPadded)
|
||||
newb.meta = setByte(defaultMeta, h2, 0)
|
||||
newb.entries[0] = entryPtr
|
||||
b.next = unsafe.Pointer(newb)
|
||||
return
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
}
|
||||
|
||||
func (table *mapOfTable[K, V]) addSize(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
atomic.AddInt64(&table.size[cidx].c, int64(delta))
|
||||
}
|
||||
|
||||
func (table *mapOfTable[K, V]) addSizePlain(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
table.size[cidx].c += int64(delta)
|
||||
}
|
||||
|
||||
func (table *mapOfTable[K, V]) sumSize() int64 {
|
||||
sum := int64(0)
|
||||
for i := range table.size {
|
||||
sum += atomic.LoadInt64(&table.size[i].c)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func h1(h uint64) uint64 {
|
||||
return h >> 7
|
||||
}
|
||||
|
||||
func h2(h uint64) uint8 {
|
||||
return uint8(h & 0x7f)
|
||||
}
|
||||
|
||||
// Stats returns statistics for the MapOf. Just like other map
|
||||
// methods, this one is thread-safe. Yet it's an O(N) operation,
|
||||
// so it should be used only for diagnostics or debugging purposes.
|
||||
func (m *MapOf[K, V]) Stats() MapStats {
|
||||
stats := MapStats{
|
||||
TotalGrowths: atomic.LoadInt64(&m.totalGrowths),
|
||||
TotalShrinks: atomic.LoadInt64(&m.totalShrinks),
|
||||
MinEntries: math.MaxInt32,
|
||||
}
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
stats.RootBuckets = len(table.buckets)
|
||||
stats.Counter = int(table.sumSize())
|
||||
stats.CounterLen = len(table.size)
|
||||
for i := range table.buckets {
|
||||
nentries := 0
|
||||
b := &table.buckets[i]
|
||||
stats.TotalBuckets++
|
||||
for {
|
||||
nentriesLocal := 0
|
||||
stats.Capacity += entriesPerMapOfBucket
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if atomic.LoadPointer(&b.entries[i]) != nil {
|
||||
stats.Size++
|
||||
nentriesLocal++
|
||||
}
|
||||
}
|
||||
nentries += nentriesLocal
|
||||
if nentriesLocal == 0 {
|
||||
stats.EmptyBuckets++
|
||||
}
|
||||
if b.next == nil {
|
||||
break
|
||||
}
|
||||
b = (*bucketOfPadded)(atomic.LoadPointer(&b.next))
|
||||
stats.TotalBuckets++
|
||||
}
|
||||
if nentries < stats.MinEntries {
|
||||
stats.MinEntries = nentries
|
||||
}
|
||||
if nentries > stats.MaxEntries {
|
||||
stats.MaxEntries = nentries
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue