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 }