| |
| |
| package ringbuffer |
|
|
| import ( |
| "runtime" |
| "sync/atomic" |
| ) |
|
|
| const ( |
| |
| CacheLineSize = 64 |
| ) |
|
|
| |
| |
| |
| type RingBuffer[T any] struct { |
| |
| _ [CacheLineSize]byte |
|
|
| |
| head atomic.Uint64 |
|
|
| _ [CacheLineSize - 8]byte |
|
|
| |
| tail atomic.Uint64 |
|
|
| _ [CacheLineSize - 8]byte |
|
|
| buffer []T |
| mask uint64 |
| } |
|
|
| |
| func New[T any](capacity int) *RingBuffer[T] { |
| if capacity <= 0 { |
| capacity = 1024 |
| } |
|
|
| |
| capacity = roundUpPowerOf2(capacity) |
|
|
| return &RingBuffer[T]{ |
| buffer: make([]T, capacity), |
| mask: uint64(capacity - 1), |
| } |
| } |
|
|
| |
| |
| |
| func (r *RingBuffer[T]) Push(item T) bool { |
| tail := r.tail.Load() |
| head := r.head.Load() |
|
|
| |
| if tail-head >= uint64(len(r.buffer)) { |
| return false |
| } |
|
|
| |
| r.buffer[tail&r.mask] = item |
|
|
| |
| r.tail.Store(tail + 1) |
|
|
| return true |
| } |
|
|
| |
| |
| |
| func (r *RingBuffer[T]) Pop() (T, bool) { |
| var zero T |
|
|
| head := r.head.Load() |
| tail := r.tail.Load() |
|
|
| |
| if head == tail { |
| return zero, false |
| } |
|
|
| |
| item := r.buffer[head&r.mask] |
|
|
| |
| r.head.Store(head + 1) |
|
|
| return item, true |
| } |
|
|
| |
| func (r *RingBuffer[T]) TryPush(item T, spins int) bool { |
| for i := 0; i < spins; i++ { |
| if r.Push(item) { |
| return true |
| } |
| runtime.Gosched() |
| } |
| return false |
| } |
|
|
| |
| func (r *RingBuffer[T]) TryPop(spins int) (T, bool) { |
| var zero T |
| for i := 0; i < spins; i++ { |
| if item, ok := r.Pop(); ok { |
| return item, true |
| } |
| runtime.Gosched() |
| } |
| return zero, false |
| } |
|
|
| |
| func (r *RingBuffer[T]) Len() int { |
| return int(r.tail.Load() - r.head.Load()) |
| } |
|
|
| |
| func (r *RingBuffer[T]) Cap() int { |
| return len(r.buffer) |
| } |
|
|
| |
| func (r *RingBuffer[T]) IsEmpty() bool { |
| return r.head.Load() == r.tail.Load() |
| } |
|
|
| |
| func (r *RingBuffer[T]) IsFull() bool { |
| return r.tail.Load()-r.head.Load() >= uint64(len(r.buffer)) |
| } |
|
|
| |
| func (r *RingBuffer[T]) Reset() { |
| r.head.Store(0) |
| r.tail.Store(0) |
| for i := range r.buffer { |
| var zero T |
| r.buffer[i] = zero |
| } |
| } |
|
|
| |
| |
| type MPMCQueue[T any] struct { |
| _ [CacheLineSize]byte |
| slots []slot[T] |
| mask uint64 |
| sendx atomic.Uint64 |
| recvx atomic.Uint64 |
| _ [CacheLineSize]byte |
| } |
|
|
| type slot[T any] struct { |
| turn atomic.Uint64 |
| item T |
| _ [CacheLineSize - 16]byte |
| } |
|
|
| |
| func NewMPMC[T any](capacity int) *MPMCQueue[T] { |
| if capacity <= 0 { |
| capacity = 1024 |
| } |
| capacity = roundUpPowerOf2(capacity) |
|
|
| q := &MPMCQueue[T]{ |
| slots: make([]slot[T], capacity), |
| mask: uint64(capacity - 1), |
| } |
|
|
| |
| for i := range q.slots { |
| q.slots[i].turn.Store(uint64(i)) |
| } |
|
|
| return q |
| } |
|
|
| |
| func (q *MPMCQueue[T]) Enqueue(item T) bool { |
| for { |
| sendx := q.sendx.Load() |
| slot := &q.slots[sendx&q.mask] |
| turn := slot.turn.Load() |
|
|
| if turn == sendx { |
| if q.sendx.CompareAndSwap(sendx, sendx+1) { |
| slot.item = item |
| slot.turn.Store(sendx + 1) |
| return true |
| } |
| } else if turn < sendx { |
| |
| return false |
| } |
| |
| runtime.Gosched() |
| } |
| } |
|
|
| |
| func (q *MPMCQueue[T]) Dequeue() (T, bool) { |
| var zero T |
|
|
| for { |
| recvx := q.recvx.Load() |
| slot := &q.slots[recvx&q.mask] |
| turn := slot.turn.Load() |
|
|
| if turn == recvx+1 { |
| if q.recvx.CompareAndSwap(recvx, recvx+1) { |
| item := slot.item |
| var empty T |
| slot.item = empty |
| slot.turn.Store(recvx + q.mask + 1) |
| return item, true |
| } |
| } else if turn < recvx+1 { |
| |
| return zero, false |
| } |
| |
| runtime.Gosched() |
| } |
| } |
|
|
| |
| func (q *MPMCQueue[T]) Len() int { |
| return int(q.sendx.Load() - q.recvx.Load()) |
| } |
|
|
| func roundUpPowerOf2(n int) int { |
| if n <= 0 { |
| return 1 |
| } |
| if n&(n-1) == 0 { |
| return n |
| } |
| p := 1 |
| for p < n { |
| p <<= 1 |
| } |
| return p |
| } |
|
|