├── .github └── workflows │ └── golang-ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── queue.go ├── queue_test.go └── v2 ├── go.mod ├── queue.go └── queue_test.go /.github/workflows/golang-ci.yml: -------------------------------------------------------------------------------- 1 | name: Golang CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | golang-version: ['1.2', '1.20'] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Golang 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: ${{ matrix.golang-version }} 22 | - name: Run tests 23 | run: go test 24 | 25 | test-v2: 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | golang-version: ['1.18', '1.20'] 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: Set up Golang 34 | uses: actions/setup-go@v4 35 | with: 36 | go-version: ${{ matrix.golang-version }} 37 | - name: Run tests 38 | run: cd v2 && go test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Evan Huus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Queue 2 | ===== 3 | 4 | [![Build Status](https://github.com/eapache/queue/actions/workflows/golang-ci.yml/badge.svg)](https://github.com/eapache/queue/actions/workflows/golang-ci.yml) 5 | [![GoDoc](https://godoc.org/github.com/eapache/queue?status.svg)](https://godoc.org/github.com/eapache/queue) 6 | [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) 7 | 8 | A fast Golang queue using a ring-buffer, based on the version suggested by Dariusz Górecki. 9 | Using this instead of other, simpler, queue implementations (slice+append or linked list) provides 10 | substantial memory and time benefits, and fewer GC pauses. 11 | 12 | The queue implemented here is as fast as it is in part because it is *not* thread-safe. 13 | 14 | The `v2` subfolder requires Go 1.18 or later and makes use of generics. 15 | 16 | Follows semantic versioning using https://gopkg.in/ - import from 17 | [`gopkg.in/eapache/queue.v1`](https://gopkg.in/eapache/queue.v1) 18 | for guaranteed API stability. 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eapache/queue 2 | 3 | go 1.2 4 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package queue provides a fast, ring-buffer queue based on the version suggested by Dariusz Górecki. 3 | Using this instead of other, simpler, queue implementations (slice+append or linked list) provides 4 | substantial memory and time benefits, and fewer GC pauses. 5 | 6 | The queue implemented here is as fast as it is for an additional reason: it is *not* thread-safe. 7 | */ 8 | package queue 9 | 10 | // minQueueLen is smallest capacity that queue may have. 11 | // Must be power of 2 for bitwise modulus: x % n == x & (n - 1). 12 | const minQueueLen = 16 13 | 14 | // Queue represents a single instance of the queue data structure. 15 | type Queue struct { 16 | buf []interface{} 17 | head, tail, count int 18 | } 19 | 20 | // New constructs and returns a new Queue. 21 | func New() *Queue { 22 | return &Queue{ 23 | buf: make([]interface{}, minQueueLen), 24 | } 25 | } 26 | 27 | // Length returns the number of elements currently stored in the queue. 28 | func (q *Queue) Length() int { 29 | return q.count 30 | } 31 | 32 | // resizes the queue to fit exactly twice its current contents 33 | // this can result in shrinking if the queue is less than half-full 34 | func (q *Queue) resize() { 35 | newBuf := make([]interface{}, q.count<<1) 36 | 37 | if q.tail > q.head { 38 | copy(newBuf, q.buf[q.head:q.tail]) 39 | } else { 40 | n := copy(newBuf, q.buf[q.head:]) 41 | copy(newBuf[n:], q.buf[:q.tail]) 42 | } 43 | 44 | q.head = 0 45 | q.tail = q.count 46 | q.buf = newBuf 47 | } 48 | 49 | // Add puts an element on the end of the queue. 50 | func (q *Queue) Add(elem interface{}) { 51 | if q.count == len(q.buf) { 52 | q.resize() 53 | } 54 | 55 | q.buf[q.tail] = elem 56 | // bitwise modulus 57 | q.tail = (q.tail + 1) & (len(q.buf) - 1) 58 | q.count++ 59 | } 60 | 61 | // Peek returns the element at the head of the queue. This call panics 62 | // if the queue is empty. 63 | func (q *Queue) Peek() interface{} { 64 | if q.count <= 0 { 65 | panic("queue: Peek() called on empty queue") 66 | } 67 | return q.buf[q.head] 68 | } 69 | 70 | // Get returns the element at index i in the queue. If the index is 71 | // invalid, the call will panic. This method accepts both positive and 72 | // negative index values. Index 0 refers to the first element, and 73 | // index -1 refers to the last. 74 | func (q *Queue) Get(i int) interface{} { 75 | // If indexing backwards, convert to positive index. 76 | if i < 0 { 77 | i += q.count 78 | } 79 | if i < 0 || i >= q.count { 80 | panic("queue: Get() called with index out of range") 81 | } 82 | // bitwise modulus 83 | return q.buf[(q.head+i)&(len(q.buf)-1)] 84 | } 85 | 86 | // Remove removes and returns the element from the front of the queue. If the 87 | // queue is empty, the call will panic. 88 | func (q *Queue) Remove() interface{} { 89 | if q.count <= 0 { 90 | panic("queue: Remove() called on empty queue") 91 | } 92 | ret := q.buf[q.head] 93 | q.buf[q.head] = nil 94 | // bitwise modulus 95 | q.head = (q.head + 1) & (len(q.buf) - 1) 96 | q.count-- 97 | // Resize down if buffer 1/4 full. 98 | if len(q.buf) > minQueueLen && (q.count<<2) == len(q.buf) { 99 | q.resize() 100 | } 101 | return ret 102 | } 103 | -------------------------------------------------------------------------------- /queue_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import "testing" 4 | 5 | func TestQueueSimple(t *testing.T) { 6 | q := New() 7 | 8 | for i := 0; i < minQueueLen; i++ { 9 | q.Add(i) 10 | } 11 | for i := 0; i < minQueueLen; i++ { 12 | if q.Peek().(int) != i { 13 | t.Error("peek", i, "had value", q.Peek()) 14 | } 15 | x := q.Remove() 16 | if x != i { 17 | t.Error("remove", i, "had value", x) 18 | } 19 | } 20 | } 21 | 22 | func TestQueueWrapping(t *testing.T) { 23 | q := New() 24 | 25 | for i := 0; i < minQueueLen; i++ { 26 | q.Add(i) 27 | } 28 | for i := 0; i < 3; i++ { 29 | q.Remove() 30 | q.Add(minQueueLen + i) 31 | } 32 | 33 | for i := 0; i < minQueueLen; i++ { 34 | if q.Peek().(int) != i+3 { 35 | t.Error("peek", i, "had value", q.Peek()) 36 | } 37 | q.Remove() 38 | } 39 | } 40 | 41 | func TestQueueLength(t *testing.T) { 42 | q := New() 43 | 44 | if q.Length() != 0 { 45 | t.Error("empty queue length not 0") 46 | } 47 | 48 | for i := 0; i < 1000; i++ { 49 | q.Add(i) 50 | if q.Length() != i+1 { 51 | t.Error("adding: queue with", i, "elements has length", q.Length()) 52 | } 53 | } 54 | for i := 0; i < 1000; i++ { 55 | q.Remove() 56 | if q.Length() != 1000-i-1 { 57 | t.Error("removing: queue with", 1000-i-i, "elements has length", q.Length()) 58 | } 59 | } 60 | } 61 | 62 | func TestQueueGet(t *testing.T) { 63 | q := New() 64 | 65 | for i := 0; i < 1000; i++ { 66 | q.Add(i) 67 | for j := 0; j < q.Length(); j++ { 68 | if q.Get(j).(int) != j { 69 | t.Errorf("index %d doesn't contain %d", j, j) 70 | } 71 | } 72 | } 73 | } 74 | 75 | func TestQueueGetNegative(t *testing.T) { 76 | q := New() 77 | 78 | for i := 0; i < 1000; i++ { 79 | q.Add(i) 80 | for j := 1; j <= q.Length(); j++ { 81 | if q.Get(-j).(int) != q.Length()-j { 82 | t.Errorf("index %d doesn't contain %d", -j, q.Length()-j) 83 | } 84 | } 85 | } 86 | } 87 | 88 | func TestQueueGetOutOfRangePanics(t *testing.T) { 89 | q := New() 90 | 91 | q.Add(1) 92 | q.Add(2) 93 | q.Add(3) 94 | 95 | assertPanics(t, "should panic when negative index", func() { 96 | q.Get(-4) 97 | }) 98 | 99 | assertPanics(t, "should panic when index greater than length", func() { 100 | q.Get(4) 101 | }) 102 | } 103 | 104 | func TestQueuePeekOutOfRangePanics(t *testing.T) { 105 | q := New() 106 | 107 | assertPanics(t, "should panic when peeking empty queue", func() { 108 | q.Peek() 109 | }) 110 | 111 | q.Add(1) 112 | q.Remove() 113 | 114 | assertPanics(t, "should panic when peeking emptied queue", func() { 115 | q.Peek() 116 | }) 117 | } 118 | 119 | func TestQueueRemoveOutOfRangePanics(t *testing.T) { 120 | q := New() 121 | 122 | assertPanics(t, "should panic when removing empty queue", func() { 123 | q.Remove() 124 | }) 125 | 126 | q.Add(1) 127 | q.Remove() 128 | 129 | assertPanics(t, "should panic when removing emptied queue", func() { 130 | q.Remove() 131 | }) 132 | } 133 | 134 | func assertPanics(t *testing.T, name string, f func()) { 135 | defer func() { 136 | if r := recover(); r == nil { 137 | t.Errorf("%s: didn't panic as expected", name) 138 | } 139 | }() 140 | 141 | f() 142 | } 143 | 144 | // WARNING: Go's benchmark utility (go test -bench .) increases the number of 145 | // iterations until the benchmarks take a reasonable amount of time to run; memory usage 146 | // is *NOT* considered. On a fast CPU, these benchmarks can fill hundreds of GB of memory 147 | // (and then hang when they start to swap). You can manually control the number of iterations 148 | // with the `-benchtime` argument. Passing `-benchtime 1000000x` seems to be about right. 149 | 150 | func BenchmarkQueueSerial(b *testing.B) { 151 | q := New() 152 | for i := 0; i < b.N; i++ { 153 | q.Add(nil) 154 | } 155 | for i := 0; i < b.N; i++ { 156 | q.Peek() 157 | q.Remove() 158 | } 159 | } 160 | 161 | func BenchmarkQueueGet(b *testing.B) { 162 | q := New() 163 | for i := 0; i < b.N; i++ { 164 | q.Add(i) 165 | } 166 | b.ResetTimer() 167 | for i := 0; i < b.N; i++ { 168 | q.Get(i) 169 | } 170 | } 171 | 172 | func BenchmarkQueueTickTock(b *testing.B) { 173 | q := New() 174 | for i := 0; i < b.N; i++ { 175 | q.Add(nil) 176 | q.Peek() 177 | q.Remove() 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eapache/queue/v2 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /v2/queue.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package queue provides a fast, ring-buffer queue based on the version suggested by Dariusz Górecki. 3 | Using this instead of other, simpler, queue implementations (slice+append or linked list) provides 4 | substantial memory and time benefits, and fewer GC pauses. 5 | 6 | The queue implemented here is as fast as it is for an additional reason: it is *not* thread-safe. 7 | */ 8 | package queue 9 | 10 | // minQueueLen is smallest capacity that queue may have. 11 | // Must be power of 2 for bitwise modulus: x % n == x & (n - 1). 12 | const minQueueLen = 16 13 | 14 | // Queue represents a single instance of the queue data structure. 15 | type Queue[V any] struct { 16 | buf []*V 17 | head, tail, count int 18 | } 19 | 20 | // New constructs and returns a new Queue. 21 | func New[V any]() *Queue[V] { 22 | return &Queue[V]{ 23 | buf: make([]*V, minQueueLen), 24 | } 25 | } 26 | 27 | // Length returns the number of elements currently stored in the queue. 28 | func (q *Queue[V]) Length() int { 29 | return q.count 30 | } 31 | 32 | // resizes the queue to fit exactly twice its current contents 33 | // this can result in shrinking if the queue is less than half-full 34 | func (q *Queue[V]) resize() { 35 | newBuf := make([]*V, q.count<<1) 36 | 37 | if q.tail > q.head { 38 | copy(newBuf, q.buf[q.head:q.tail]) 39 | } else { 40 | n := copy(newBuf, q.buf[q.head:]) 41 | copy(newBuf[n:], q.buf[:q.tail]) 42 | } 43 | 44 | q.head = 0 45 | q.tail = q.count 46 | q.buf = newBuf 47 | } 48 | 49 | // Add puts an element on the end of the queue. 50 | func (q *Queue[V]) Add(elem V) { 51 | if q.count == len(q.buf) { 52 | q.resize() 53 | } 54 | 55 | q.buf[q.tail] = &elem 56 | // bitwise modulus 57 | q.tail = (q.tail + 1) & (len(q.buf) - 1) 58 | q.count++ 59 | } 60 | 61 | // Peek returns the element at the head of the queue. This call panics 62 | // if the queue is empty. 63 | func (q *Queue[V]) Peek() V { 64 | if q.count <= 0 { 65 | panic("queue: Peek() called on empty queue") 66 | } 67 | return *(q.buf[q.head]) 68 | } 69 | 70 | // Get returns the element at index i in the queue. If the index is 71 | // invalid, the call will panic. This method accepts both positive and 72 | // negative index values. Index 0 refers to the first element, and 73 | // index -1 refers to the last. 74 | func (q *Queue[V]) Get(i int) V { 75 | // If indexing backwards, convert to positive index. 76 | if i < 0 { 77 | i += q.count 78 | } 79 | if i < 0 || i >= q.count { 80 | panic("queue: Get() called with index out of range") 81 | } 82 | // bitwise modulus 83 | return *(q.buf[(q.head+i)&(len(q.buf)-1)]) 84 | } 85 | 86 | // Remove removes and returns the element from the front of the queue. If the 87 | // queue is empty, the call will panic. 88 | func (q *Queue[V]) Remove() V { 89 | if q.count <= 0 { 90 | panic("queue: Remove() called on empty queue") 91 | } 92 | ret := q.buf[q.head] 93 | q.buf[q.head] = nil 94 | // bitwise modulus 95 | q.head = (q.head + 1) & (len(q.buf) - 1) 96 | q.count-- 97 | // Resize down if buffer 1/4 full. 98 | if len(q.buf) > minQueueLen && (q.count<<2) == len(q.buf) { 99 | q.resize() 100 | } 101 | return *ret 102 | } 103 | -------------------------------------------------------------------------------- /v2/queue_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import "testing" 4 | 5 | func TestQueueSimple(t *testing.T) { 6 | q := New[int]() 7 | 8 | for i := 0; i < minQueueLen; i++ { 9 | q.Add(i) 10 | } 11 | for i := 0; i < minQueueLen; i++ { 12 | if q.Peek() != i { 13 | t.Error("peek", i, "had value", q.Peek()) 14 | } 15 | x := q.Remove() 16 | if x != i { 17 | t.Error("remove", i, "had value", x) 18 | } 19 | } 20 | } 21 | 22 | func TestQueueWrapping(t *testing.T) { 23 | q := New[int]() 24 | 25 | for i := 0; i < minQueueLen; i++ { 26 | q.Add(i) 27 | } 28 | for i := 0; i < 3; i++ { 29 | q.Remove() 30 | q.Add(minQueueLen + i) 31 | } 32 | 33 | for i := 0; i < minQueueLen; i++ { 34 | if q.Peek() != i+3 { 35 | t.Error("peek", i, "had value", q.Peek()) 36 | } 37 | q.Remove() 38 | } 39 | } 40 | 41 | func TestQueueLength(t *testing.T) { 42 | q := New[int]() 43 | 44 | if q.Length() != 0 { 45 | t.Error("empty queue length not 0") 46 | } 47 | 48 | for i := 0; i < 1000; i++ { 49 | q.Add(i) 50 | if q.Length() != i+1 { 51 | t.Error("adding: queue with", i, "elements has length", q.Length()) 52 | } 53 | } 54 | for i := 0; i < 1000; i++ { 55 | q.Remove() 56 | if q.Length() != 1000-i-1 { 57 | t.Error("removing: queue with", 1000-i-i, "elements has length", q.Length()) 58 | } 59 | } 60 | } 61 | 62 | func TestQueueGet(t *testing.T) { 63 | q := New[int]() 64 | 65 | for i := 0; i < 1000; i++ { 66 | q.Add(i) 67 | for j := 0; j < q.Length(); j++ { 68 | if q.Get(j) != j { 69 | t.Errorf("index %d doesn't contain %d", j, j) 70 | } 71 | } 72 | } 73 | } 74 | 75 | func TestQueueGetNegative(t *testing.T) { 76 | q := New[int]() 77 | 78 | for i := 0; i < 1000; i++ { 79 | q.Add(i) 80 | for j := 1; j <= q.Length(); j++ { 81 | if q.Get(-j) != q.Length()-j { 82 | t.Errorf("index %d doesn't contain %d", -j, q.Length()-j) 83 | } 84 | } 85 | } 86 | } 87 | 88 | func TestQueueGetOutOfRangePanics(t *testing.T) { 89 | q := New[int]() 90 | 91 | q.Add(1) 92 | q.Add(2) 93 | q.Add(3) 94 | 95 | assertPanics(t, "should panic when negative index", func() { 96 | q.Get(-4) 97 | }) 98 | 99 | assertPanics(t, "should panic when index greater than length", func() { 100 | q.Get(4) 101 | }) 102 | } 103 | 104 | func TestQueuePeekOutOfRangePanics(t *testing.T) { 105 | q := New[any]() 106 | 107 | assertPanics(t, "should panic when peeking empty queue", func() { 108 | q.Peek() 109 | }) 110 | 111 | q.Add(1) 112 | q.Remove() 113 | 114 | assertPanics(t, "should panic when peeking emptied queue", func() { 115 | q.Peek() 116 | }) 117 | } 118 | 119 | func TestQueueRemoveOutOfRangePanics(t *testing.T) { 120 | q := New[int]() 121 | 122 | assertPanics(t, "should panic when removing empty queue", func() { 123 | q.Remove() 124 | }) 125 | 126 | q.Add(1) 127 | q.Remove() 128 | 129 | assertPanics(t, "should panic when removing emptied queue", func() { 130 | q.Remove() 131 | }) 132 | } 133 | 134 | func assertPanics(t *testing.T, name string, f func()) { 135 | defer func() { 136 | if r := recover(); r == nil { 137 | t.Errorf("%s: didn't panic as expected", name) 138 | } 139 | }() 140 | 141 | f() 142 | } 143 | 144 | // WARNING: Go's benchmark utility (go test -bench .) increases the number of 145 | // iterations until the benchmarks take a reasonable amount of time to run; memory usage 146 | // is *NOT* considered. On a fast CPU, these benchmarks can fill hundreds of GB of memory 147 | // (and then hang when they start to swap). You can manually control the number of iterations 148 | // with the `-benchtime` argument. Passing `-benchtime 1000000x` seems to be about right. 149 | 150 | func BenchmarkQueueSerial(b *testing.B) { 151 | q := New[any]() 152 | for i := 0; i < b.N; i++ { 153 | q.Add(nil) 154 | } 155 | for i := 0; i < b.N; i++ { 156 | q.Peek() 157 | q.Remove() 158 | } 159 | } 160 | 161 | func BenchmarkQueueGet(b *testing.B) { 162 | q := New[int]() 163 | for i := 0; i < b.N; i++ { 164 | q.Add(i) 165 | } 166 | b.ResetTimer() 167 | for i := 0; i < b.N; i++ { 168 | q.Get(i) 169 | } 170 | } 171 | 172 | func BenchmarkQueueTickTock(b *testing.B) { 173 | q := New[any]() 174 | for i := 0; i < b.N; i++ { 175 | q.Add(nil) 176 | q.Peek() 177 | q.Remove() 178 | } 179 | } 180 | --------------------------------------------------------------------------------