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:
Glonee
2023-06-02 02:53:37 +08:00
committed by GitHub
parent 9237dbb167
commit f1f42d8d90
5 changed files with 157 additions and 8 deletions

View 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
}

View 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()
}
}

View 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")
}

View 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))
})
})