forked from quic-go/quic-go
use a ring buffer in the framer (#3857)
* implement and use ringbuffer in framer * Add comments for ring buffer Co-authored-by: Marten Seemann <martenseemann@gmail.com> --------- Co-authored-by: Marten Seemann <martenseemann@gmail.com>
This commit is contained in:
86
internal/utils/ringbuffer/ringbuffer.go
Normal file
86
internal/utils/ringbuffer/ringbuffer.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package ringbuffer
|
||||
|
||||
// A RingBuffer is a ring buffer.
|
||||
// It acts as a heap that doesn't cause any allocations.
|
||||
type RingBuffer[T any] struct {
|
||||
ring []T
|
||||
headPos, tailPos int
|
||||
full bool
|
||||
}
|
||||
|
||||
// Init preallocs a buffer with a certain size.
|
||||
func (r *RingBuffer[T]) Init(size int) {
|
||||
r.ring = make([]T, size)
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the ring buffer.
|
||||
func (r *RingBuffer[T]) Len() int {
|
||||
if r.full {
|
||||
return len(r.ring)
|
||||
}
|
||||
if r.tailPos >= r.headPos {
|
||||
return r.tailPos - r.headPos
|
||||
}
|
||||
return r.tailPos - r.headPos + len(r.ring)
|
||||
}
|
||||
|
||||
// Empty says if the ring buffer is empty.
|
||||
func (r *RingBuffer[T]) Empty() bool {
|
||||
return !r.full && r.headPos == r.tailPos
|
||||
}
|
||||
|
||||
// PushBack adds a new element.
|
||||
// If the ring buffer is full, its capacity is increased first.
|
||||
func (r *RingBuffer[T]) PushBack(t T) {
|
||||
if r.full || len(r.ring) == 0 {
|
||||
r.grow()
|
||||
}
|
||||
r.ring[r.tailPos] = t
|
||||
r.tailPos++
|
||||
if r.tailPos == len(r.ring) {
|
||||
r.tailPos = 0
|
||||
}
|
||||
if r.tailPos == r.headPos {
|
||||
r.full = true
|
||||
}
|
||||
}
|
||||
|
||||
// PopFront returns the next element.
|
||||
// It must not be called when the buffer is empty, that means that
|
||||
// callers might need to check if there are elements in the buffer first.
|
||||
func (r *RingBuffer[T]) PopFront() T {
|
||||
if r.Empty() {
|
||||
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: pop from an empty queue")
|
||||
}
|
||||
r.full = false
|
||||
t := r.ring[r.headPos]
|
||||
r.ring[r.headPos] = *new(T)
|
||||
r.headPos++
|
||||
if r.headPos == len(r.ring) {
|
||||
r.headPos = 0
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Grow the maximum size of the queue.
|
||||
// This method assume the queue is full.
|
||||
func (r *RingBuffer[T]) grow() {
|
||||
oldRing := r.ring
|
||||
newSize := len(oldRing) * 2
|
||||
if newSize == 0 {
|
||||
newSize = 1
|
||||
}
|
||||
r.ring = make([]T, newSize)
|
||||
headLen := copy(r.ring, oldRing[r.headPos:])
|
||||
copy(r.ring[headLen:], oldRing[:r.headPos])
|
||||
r.headPos, r.tailPos, r.full = 0, len(oldRing), false
|
||||
}
|
||||
|
||||
// Clear removes all elements.
|
||||
func (r *RingBuffer[T]) Clear() {
|
||||
var zeroValue T
|
||||
for i := range r.ring {
|
||||
r.ring[i] = zeroValue
|
||||
}
|
||||
r.headPos, r.tailPos, r.full = 0, 0, false
|
||||
}
|
||||
12
internal/utils/ringbuffer/ringbuffer_bench_test.go
Normal file
12
internal/utils/ringbuffer/ringbuffer_bench_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package ringbuffer
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkRingBuffer(b *testing.B) {
|
||||
r := RingBuffer[int]{}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.PushBack(i)
|
||||
r.PopFront()
|
||||
}
|
||||
}
|
||||
13
internal/utils/ringbuffer/ringbuffer_suite_test.go
Normal file
13
internal/utils/ringbuffer/ringbuffer_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package ringbuffer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestTestdata(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "ringbuffer suite")
|
||||
}
|
||||
38
internal/utils/ringbuffer/ringbuffer_test.go
Normal file
38
internal/utils/ringbuffer/ringbuffer_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package ringbuffer
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("RingBuffer", func() {
|
||||
It("push and pop", func() {
|
||||
r := RingBuffer[int]{}
|
||||
Expect(len(r.ring)).To(Equal(0))
|
||||
Expect(func() { r.PopFront() }).To(Panic())
|
||||
r.PushBack(1)
|
||||
r.PushBack(2)
|
||||
r.PushBack(3)
|
||||
Expect(r.PopFront()).To(Equal(1))
|
||||
Expect(r.PopFront()).To(Equal(2))
|
||||
r.PushBack(4)
|
||||
r.PushBack(5)
|
||||
Expect(r.Len()).To(Equal(3))
|
||||
r.PushBack(6)
|
||||
Expect(r.Len()).To(Equal(4))
|
||||
Expect(r.PopFront()).To(Equal(3))
|
||||
Expect(r.PopFront()).To(Equal(4))
|
||||
Expect(r.PopFront()).To(Equal(5))
|
||||
Expect(r.PopFront()).To(Equal(6))
|
||||
})
|
||||
It("clear", func() {
|
||||
r := RingBuffer[int]{}
|
||||
r.Init(2)
|
||||
r.PushBack(1)
|
||||
r.PushBack(2)
|
||||
Expect(r.full).To(BeTrue())
|
||||
r.Clear()
|
||||
Expect(r.full).To(BeFalse())
|
||||
Expect(r.Len()).To(Equal(0))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user