Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use circular queue for idle connections
- Loading branch information
Showing
3 changed files
with
227 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package circ | ||
|
||
type Queue[T any] struct { | ||
arr []T | ||
begin int | ||
len int | ||
} | ||
|
||
func NewQueue[T any](capacity int) *Queue[T] { | ||
return &Queue[T]{ | ||
// TODO: Do not preallocate whole capacity of Go upstream | ||
// accepts this: https://github.com/golang/go/issues/55978 | ||
arr: make([]T, capacity), | ||
} | ||
} | ||
|
||
func (q *Queue[T]) Cap() int { return len(q.arr) } | ||
func (q *Queue[T]) Len() int { return q.len } | ||
|
||
func (q *Queue[T]) end() int { | ||
e := q.begin + q.len | ||
if l := len(q.arr); e >= l { | ||
e -= l | ||
} | ||
|
||
return e | ||
} | ||
|
||
func (q *Queue[T]) Enqueue(elem T) { | ||
if q.len == len(q.arr) { | ||
panic("enqueue: queue is full") | ||
} | ||
|
||
q.arr[q.end()] = elem | ||
q.len++ | ||
} | ||
|
||
func (q *Queue[T]) Dequeue() T { | ||
if q.len < 1 { | ||
panic("dequeue: queue is empty") | ||
} | ||
|
||
elem := q.arr[q.begin] | ||
|
||
// Avoid memory leaks if T is pointer or contains pointers. | ||
var zeroVal T | ||
q.arr[q.begin] = zeroVal | ||
|
||
q.len-- | ||
q.begin++ | ||
if q.begin == len(q.arr) { | ||
q.begin = 0 | ||
} | ||
|
||
return elem | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package circ_test | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"testing" | ||
|
||
"github.com/jackc/puddle/v2/internal/circ" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestQueue_EnqueueDequeue(t *testing.T) { | ||
r := require.New(t) | ||
|
||
q := circ.NewQueue[int](10) | ||
r.Equal(10, q.Cap()) | ||
|
||
for i := 0; i < 10; i++ { | ||
q.Enqueue(i) | ||
r.Equal(i+1, q.Len()) | ||
} | ||
|
||
r.Panics(func() { q.Enqueue(10) }) | ||
r.Equal(10, q.Len()) | ||
|
||
for i := 0; i < 10; i++ { | ||
j := q.Dequeue() | ||
r.Equal(i, j) | ||
|
||
r.Equal(10-i-1, q.Len()) | ||
} | ||
|
||
r.Panics(func() { q.Dequeue() }) | ||
r.Equal(0, q.Len()) | ||
} | ||
|
||
func TestQueue_EnqueueDequeueOverflow(t *testing.T) { | ||
r := require.New(t) | ||
|
||
q := circ.NewQueue[int](10) | ||
r.Equal(10, q.Cap()) | ||
|
||
for i := 0; i < 10; i++ { | ||
q.Enqueue(i) | ||
r.Equal(i+1, q.Len()) | ||
} | ||
|
||
r.Panics(func() { q.Enqueue(10) }) | ||
r.Equal(10, q.Len()) | ||
|
||
for i := 0; i < 5; i++ { | ||
j := q.Dequeue() | ||
r.Equal(i, j) | ||
|
||
r.Equal(10-i-1, q.Len()) | ||
} | ||
|
||
for i := 10; i < 15; i++ { | ||
q.Enqueue(i) | ||
r.Equal(i-5+1, q.Len()) | ||
} | ||
|
||
for i := 0; i < 10; i++ { | ||
j := q.Dequeue() | ||
r.Equal(i+5, j) | ||
|
||
r.Equal(10-i-1, q.Len()) | ||
} | ||
|
||
r.Panics(func() { q.Dequeue() }) | ||
r.Equal(0, q.Len()) | ||
} | ||
|
||
func BenchmarkArrayAppend(b *testing.B) { | ||
arr := make([]int, 0, b.N) | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
arr = append(arr, i) | ||
} | ||
|
||
// Make sure that the Go compiler doesn't optimize writes above. | ||
b.StopTimer() | ||
for i := 0; i < b.N; i++ { | ||
fmt.Fprintf(io.Discard, "%d\n", arr[i]) | ||
} | ||
} | ||
|
||
func BenchmarkArrayWrite(b *testing.B) { | ||
arr := make([]int, b.N) | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
arr[i] = i | ||
} | ||
|
||
// Make sure that the Go compiler doesn't optimize writes above. | ||
b.StopTimer() | ||
for i := 0; i < b.N; i++ { | ||
fmt.Fprintf(io.Discard, "%d\n", arr[i]) | ||
} | ||
} | ||
|
||
func BenchmarkEnqueue(b *testing.B) { | ||
q := circ.NewQueue[int](b.N) | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
q.Enqueue(i) | ||
} | ||
|
||
// Make sure that the Go compiler doesn't optimize writes above. | ||
b.StopTimer() | ||
for i := 0; i < b.N; i++ { | ||
fmt.Fprintf(io.Discard, "%d\n", q.Dequeue()) | ||
} | ||
} | ||
|
||
func BenchmarkChanWrite(b *testing.B) { | ||
// Chennels are another way how to represent a queue. | ||
ch := make(chan int, b.N) | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
ch <- i | ||
} | ||
|
||
// Make sure that the Go compiler doesn't optimize writes above. | ||
b.StopTimer() | ||
for i := 0; i < b.N; i++ { | ||
fmt.Fprintf(io.Discard, "%d\n", <-ch) | ||
} | ||
} | ||
|
||
func BenchmarkDequeue(b *testing.B) { | ||
q := circ.NewQueue[int](b.N) | ||
|
||
for i := 0; i < b.N; i++ { | ||
q.Enqueue(i) | ||
} | ||
|
||
out := make([]int, b.N) | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
out[i] = q.Dequeue() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters