github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/list/x_skl_utils.go (about)

     1  // list/x_skl_utils.go
     2  package list
     3  
     4  import (
     5  	"reflect"
     6  	"runtime"
     7  	"sync"
     8  	"sync/atomic"
     9  	"unsafe"
    10  
    11  	ibits "github.com/benz9527/xboot/lib/bits"
    12  	"github.com/benz9527/xboot/lib/infra"
    13  )
    14  
    15  var insertReplaceDisabled = []bool{false}
    16  
    17  var (
    18  	_ SklElement[uint8, uint8]       = (*xSklElement[uint8, uint8])(nil)
    19  	_ SklIterationItem[uint8, uint8] = (*xSklIter[uint8, uint8])(nil)
    20  )
    21  
    22  type xSklElement[K infra.OrderedKey, V any] struct {
    23  	key K
    24  	val V
    25  }
    26  
    27  func (e *xSklElement[K, V]) Key() K {
    28  	return e.key
    29  }
    30  
    31  func (e *xSklElement[K, V]) Val() V {
    32  	return e.val
    33  }
    34  
    35  type xSklIter[K infra.OrderedKey, V any] struct {
    36  	keyFn           func() K
    37  	valFn           func() V
    38  	nodeLevelFn     func() uint32
    39  	nodeItemCountFn func() int64
    40  }
    41  
    42  func (x *xSklIter[K, V]) Key() K               { return x.keyFn() }
    43  func (x *xSklIter[K, V]) Val() V               { return x.valFn() }
    44  func (x *xSklIter[K, V]) NodeLevel() uint32    { return x.nodeLevelFn() }
    45  func (x *xSklIter[K, V]) NodeItemCount() int64 { return x.nodeItemCountFn() }
    46  
    47  // Store the concurrent state.
    48  
    49  // Bit flag set from 0 to 1.
    50  func atomicSet(flagBits *uint32, bits uint32) {
    51  	for {
    52  		old := atomic.LoadUint32(flagBits)
    53  		if old&bits != bits {
    54  			n := old | bits
    55  			if atomic.CompareAndSwapUint32(flagBits, old, n) {
    56  				return
    57  			}
    58  			continue
    59  		}
    60  		return
    61  	}
    62  }
    63  
    64  func set(flagBits, bits uint32) uint32 {
    65  	return flagBits | bits
    66  }
    67  
    68  // Bit flag set from 1 to 0.
    69  func atomicUnset(flagBits *uint32, bits uint32) {
    70  	for {
    71  		old := atomic.LoadUint32(flagBits)
    72  		check := old & bits
    73  		if check != 0 {
    74  			n := old ^ check
    75  			if atomic.CompareAndSwapUint32(flagBits, old, n) {
    76  				return
    77  			}
    78  			continue
    79  		}
    80  		return
    81  	}
    82  }
    83  
    84  func atomicIsSet(flagBits *uint32, bit uint32) bool {
    85  	return (atomic.LoadUint32(flagBits) & bit) != 0
    86  }
    87  
    88  func atomicAreEqual(flagBits *uint32, bits, expect uint32) bool {
    89  	if ibits.HammingWeightBySWARV2[uint32](bits) <= 1 {
    90  		panic("it is not a multi-bits")
    91  	}
    92  	n := 0
    93  	for (bits>>n)&0x1 != 0x1 {
    94  		n++
    95  	}
    96  	if n > 0 {
    97  		expect <<= n
    98  	}
    99  	return (atomic.LoadUint32(flagBits) & bits) == expect
   100  }
   101  
   102  func atomicLoadBits(flagBits *uint32, bits uint32) uint32 {
   103  	if ibits.HammingWeightBySWARV2[uint32](bits) <= 1 {
   104  		panic("it is not a multi-bits")
   105  	}
   106  	n := 0
   107  	for (bits>>n)&0x1 != 0x1 {
   108  		n++
   109  	}
   110  	res := atomic.LoadUint32(flagBits) & bits
   111  	if n > 0 {
   112  		res >>= n
   113  	}
   114  	return res
   115  }
   116  
   117  func setBitsAs(flagBits, bits, target uint32) uint32 {
   118  	if ibits.HammingWeightBySWARV2[uint32](bits) <= 1 {
   119  		panic("it is not a multi-bits")
   120  	}
   121  	n := 0
   122  	for (bits>>n)&0x1 != 0x1 {
   123  		n++
   124  	}
   125  	if n > 0 {
   126  		target <<= n
   127  	}
   128  	check := flagBits & bits
   129  	flagBits = flagBits ^ check
   130  	return flagBits | target
   131  }
   132  
   133  func atomicSetBitsAs(flagBits *uint32, bits, target uint32) {
   134  	if ibits.HammingWeightBySWARV2[uint32](bits) <= 1 {
   135  		panic("it is not a multi-bits")
   136  	}
   137  	n := 0
   138  	for (bits>>n)&0x1 != 0x1 {
   139  		n++
   140  	}
   141  	if n > 0 {
   142  		target <<= n
   143  	}
   144  
   145  	for {
   146  		old := atomic.LoadUint32(flagBits)
   147  		check := old & bits
   148  		if check != 0 {
   149  			n := old ^ check
   150  			n = n | target
   151  			if atomic.CompareAndSwapUint32(flagBits, old, n) {
   152  				return
   153  			}
   154  			continue
   155  		}
   156  		return
   157  	}
   158  }
   159  
   160  func isSet(flagBits, bit uint32) bool {
   161  	return (flagBits & bit) != 0
   162  }
   163  
   164  func loadBits(flagBits, bits uint32) uint32 {
   165  	if ibits.HammingWeightBySWARV2[uint32](bits) <= 1 {
   166  		panic("it is not a multi-bits")
   167  	}
   168  	n := 0
   169  	for (bits>>n)&0x1 != 0x1 {
   170  		n++
   171  	}
   172  	res := flagBits & bits
   173  	if n > 0 {
   174  		res >>= n
   175  	}
   176  	return res
   177  }
   178  
   179  func areEqual(flagBits, bits, expect uint32) bool {
   180  	return loadBits(flagBits, bits) == expect
   181  }
   182  
   183  type segmentMutex interface {
   184  	lock(version uint64)
   185  	tryLock(version uint64) bool
   186  	unlock(version uint64) bool
   187  }
   188  
   189  type mutexImpl uint8
   190  
   191  const (
   192  	xSklSpinMutex mutexImpl = 1 + iota // Lock-free, spin-lock, optimistic-lock
   193  	xSklFakeMutex                      // No lock
   194  )
   195  
   196  func (mu mutexImpl) String() string {
   197  	switch mu {
   198  	case xSklSpinMutex:
   199  		return "spin"
   200  	case xSklFakeMutex:
   201  		return "fake"
   202  	default:
   203  		return "unknown"
   204  	}
   205  }
   206  
   207  type spinMutex uint64
   208  
   209  func (m *spinMutex) lock(version uint64) {
   210  	backoff := uint8(1)
   211  	for !atomic.CompareAndSwapUint64((*uint64)(m), unlocked, version) {
   212  		if backoff <= 32 {
   213  			for i := uint8(0); i < backoff; i++ {
   214  				infra.ProcYield(20)
   215  			}
   216  		} else {
   217  			runtime.Gosched()
   218  		}
   219  		backoff <<= 1
   220  	}
   221  }
   222  
   223  func (m *spinMutex) tryLock(version uint64) bool {
   224  	return atomic.CompareAndSwapUint64((*uint64)(m), unlocked, version)
   225  }
   226  
   227  func (m *spinMutex) unlock(version uint64) bool {
   228  	return atomic.CompareAndSwapUint64((*uint64)(m), version, unlocked)
   229  }
   230  
   231  type goSyncMutex struct {
   232  	mu sync.Mutex
   233  }
   234  
   235  func (m *goSyncMutex) lock(version uint64) {
   236  	m.mu.Lock()
   237  }
   238  
   239  func (m *goSyncMutex) tryLock(version uint64) bool {
   240  	return m.mu.TryLock()
   241  }
   242  
   243  func (m *goSyncMutex) unlock(version uint64) bool {
   244  	m.mu.Unlock()
   245  	return true
   246  }
   247  
   248  type fakeMutex struct{}
   249  
   250  func (m *fakeMutex) lock(version uint64)         {}
   251  func (m *fakeMutex) tryLock(version uint64) bool { return true }
   252  func (m *fakeMutex) unlock(version uint64) bool  { return true }
   253  
   254  // References:
   255  // https://github.com/ortuman/nuke
   256  // https://github.com/dgraph-io/badger/blob/master/skl/arena.go
   257  
   258  type arenaBuffer struct {
   259  	ptr      unsafe.Pointer
   260  	offset   uintptr // current index offset
   261  	cap      uintptr // capacity, indicates how many objects could be stored
   262  	objSize  uintptr // fixed object size
   263  	objAlign uintptr // fixed object alignment
   264  }
   265  
   266  func (buf *arenaBuffer) availableBytes() uintptr {
   267  	return buf.cap*buf.objSize - buf.offset
   268  }
   269  
   270  func (buf *arenaBuffer) allocate() (unsafe.Pointer, bool) {
   271  	if /* lazy init */ buf.ptr == nil {
   272  		buffer := make([]byte, buf.cap*buf.objSize)
   273  		buf.ptr = unsafe.Pointer(unsafe.SliceData(buffer))
   274  	}
   275  	alignOffset := uintptr(0)
   276  	for alignedPtr := uintptr(buf.ptr) + buf.offset; alignedPtr%buf.objAlign != 0; alignedPtr++ {
   277  		alignOffset++
   278  	}
   279  	allocatedSize := buf.objSize + alignOffset
   280  
   281  	if /* scale */ buf.availableBytes() < allocatedSize {
   282  		return nil, false
   283  	}
   284  
   285  	ptr := unsafe.Pointer(uintptr(buf.ptr) + buf.offset + alignOffset)
   286  	buf.offset += allocatedSize
   287  
   288  	// Translated into runtime.memclrNoHeapPointers by compiler.
   289  	// An assembler optimized implementation.
   290  	// go/src/runtime/memclr_$GOARCH.s (since https://codereview.appspot.com/137880043)
   291  	bytes := unsafe.Slice((*byte)(ptr), buf.objSize)
   292  	for i := range bytes {
   293  		bytes[i] = 0
   294  	}
   295  	return ptr, true
   296  }
   297  
   298  func (buf *arenaBuffer) reset() {
   299  	if buf.offset == 0 {
   300  		return
   301  	}
   302  	// Overwrite allow
   303  	buf.offset = 0
   304  }
   305  
   306  func (buf *arenaBuffer) free() {
   307  	buf.reset()
   308  	buf.ptr = nil
   309  }
   310  
   311  func newArenaBuffer(cap, size, alignment uintptr) *arenaBuffer {
   312  	return &arenaBuffer{
   313  		cap:      cap,
   314  		objSize:  size,
   315  		objAlign: alignment,
   316  		offset:   uintptr(0),
   317  	}
   318  }
   319  
   320  // T must not be a pointer type.
   321  type autoGrowthArena[T any] struct {
   322  	buffers  []*arenaBuffer
   323  	recycled []*T
   324  }
   325  
   326  func (arena *autoGrowthArena[T]) bufLen() int {
   327  	return len(arena.buffers)
   328  }
   329  
   330  func (arena *autoGrowthArena[T]) recLen() int {
   331  	return len(arena.recycled)
   332  }
   333  
   334  func (arena *autoGrowthArena[T]) objLen() uint64 {
   335  	l := uint64(0)
   336  	for i := 0; i < len(arena.buffers); i++ {
   337  		if arena.buffers[i].availableBytes() <= uintptr(0) {
   338  			l += uint64(arena.buffers[i].cap)
   339  		} else {
   340  			l += uint64(arena.buffers[i].offset / arena.buffers[i].objSize)
   341  		}
   342  	}
   343  	return l
   344  }
   345  
   346  func (arena *autoGrowthArena[T]) allocate() (*T, bool) {
   347  	var ptr unsafe.Pointer
   348  	allocated := false
   349  	for i := 0; i < len(arena.buffers); i++ {
   350  		if arena.buffers[i].availableBytes() <= uintptr(0) {
   351  			continue
   352  		} else {
   353  			ptr, allocated = arena.buffers[i].allocate()
   354  			break
   355  		}
   356  	}
   357  	rl := len(arena.recycled)
   358  	if !allocated && rl <= 0 {
   359  		buf := newArenaBuffer(
   360  			arena.buffers[0].cap,
   361  			arena.buffers[0].objSize,
   362  			arena.buffers[0].objAlign,
   363  		)
   364  		arena.buffers = append(arena.buffers, buf)
   365  		ptr, allocated = buf.allocate()
   366  	} else if !allocated && rl > 0 {
   367  		allocated = true
   368  		p := arena.recycled[0]
   369  		arena.recycled = arena.recycled[1:]
   370  		return p, allocated
   371  	}
   372  	if !allocated || ptr == nil {
   373  		return nil, false
   374  	}
   375  	return (*T)(ptr), allocated
   376  }
   377  
   378  func (arena *autoGrowthArena[T]) free() {
   379  	for _, buf := range arena.buffers {
   380  		buf.free()
   381  	}
   382  }
   383  
   384  func (arena *autoGrowthArena[T]) reset(indices ...int) {
   385  	l := len(arena.buffers)
   386  	if len(indices) > 0 {
   387  		for i := range indices {
   388  			if i < l {
   389  				arena.buffers[i].reset()
   390  			}
   391  		}
   392  		return
   393  	}
   394  	for _, buf := range arena.buffers {
   395  		buf.reset()
   396  	}
   397  }
   398  
   399  func (arena *autoGrowthArena[T]) recycle(objs ...*T) {
   400  	arena.recycled = append(arena.recycled, objs...)
   401  }
   402  
   403  func newAutoGrowthArena[T any](capPerBuf, initRecycleCap uint32) *autoGrowthArena[T] {
   404  	o := *new(T)
   405  	if reflect.TypeOf(o).Kind() == reflect.Ptr {
   406  		panic("forbid to pass ptr generic type for auto growth arena")
   407  	}
   408  
   409  	objSize, objAlign := unsafe.Sizeof(o), unsafe.Alignof(o)
   410  	buffers := make([]*arenaBuffer, 0, 32)
   411  	buffers = append(buffers, newArenaBuffer(uintptr(capPerBuf), objSize, objAlign))
   412  	return &autoGrowthArena[T]{
   413  		buffers:  buffers,
   414  		recycled: make([]*T, 0, initRecycleCap),
   415  	}
   416  }