Skip to content

Commit aa6086b

Browse files
committed
Simplify copying, exploit power-of-two length, cleanups.
1 parent ebe3f15 commit aa6086b

File tree

1 file changed

+17
-21
lines changed

1 file changed

+17
-21
lines changed

queue/queue.go

+17-21
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
// top of a slice/array. All operations are constant time except
77
// for PushFront and PushBack which are amortized constant time.
88
//
9-
// We are about 15%-45% faster than container/list at the price
10-
// of potentially wasting some memory because we grow by doubling.
11-
// We seem to even beat Go's channels by a small margin.
9+
// We are almost twice as fast as container/list at the price of
10+
// potentially wasting some memory because we grow by doubling.
11+
// We are also faster than Go's channels by a smaller margin.
1212
package queue
1313

1414
import "fmt"
@@ -18,7 +18,8 @@ import "fmt"
1818
type Queue struct {
1919
// PushBack writes to rep[back] and then increments
2020
// back; PushFront decrements front and then writes
21-
// to rep[front]; gotta love those invariants.
21+
// to rep[front]; len(rep) must be a power of two;
22+
// gotta love those invariants.
2223
rep []interface{}
2324
front int
2425
back int
@@ -32,19 +33,13 @@ func New() *Queue {
3233

3334
// Init initializes or clears queue q.
3435
func (q *Queue) Init() *Queue {
35-
// start with a slice of length 2 even if that "wastes"
36-
// some memory; we do front/back arithmetic modulo the
37-
// length, so starting at 1 would require special cases
38-
q.rep = make([]interface{}, 2)
39-
// for some time I considered reusing the existing slice
40-
// if all a client does is re-initialize the queue; the
41-
// big problem with that is that the previous queue might
42-
// have been huge while the current queue doesn't grow
43-
// much at all; if that were to happen we'd hold on to a
44-
// huge chunk of memory for just a few elements and nobody
45-
// could do anything about it; so instead I decided to
46-
// just allocate a new slice and let the GC take care of
47-
// the previous one; seems a better tradeoff all around
36+
q.rep = make([]interface{}, 1)
37+
// I considered reusing the existing slice if all a client does
38+
// is re-initialize the queue. The problem is that the current
39+
// queue might be huge, but the next one might not grow much. So
40+
// we'd hold on to a huge chunk of memory for just a few elements
41+
// and nobody can do anything. Making a new slice and letting the
42+
// GC take care of the old one seems like a better tradeoff.
4843
q.front, q.back, q.length = 0, 0, 0
4944
return q
5045
}
@@ -81,8 +76,9 @@ func (q *Queue) full() bool {
8176
func (q *Queue) grow() {
8277
bigger := make([]interface{}, q.length*2)
8378
// Kudos to Rodrigo Moraes, see https://gist.github.com/moraes/2141121
84-
copy(bigger, q.rep[q.front:])
85-
copy(bigger[q.length-q.front:], q.rep[:q.front])
79+
// Kudos to Dariusz Górecki, see https://github.com/eapache/queue/commit/334cc1b02398be651373851653017e6cbf588f9e
80+
n := copy(bigger, q.rep[q.front:])
81+
copy(bigger[n:], q.rep[:q.front])
8682
// The above replaced the "obvious" for loop and is a bit tricky.
8783
// First note that q.front == q.back if we're full; if that wasn't
8884
// true, things would be more complicated. Second recall that for
@@ -122,13 +118,13 @@ func (q *Queue) String() string {
122118
// inc returns the next integer position wrapping around queue q.
123119
func (q *Queue) inc(i int) int {
124120
l := len(q.rep)
125-
return (i + 1 + l) % l
121+
return (i + 1) & (l - 1) // requires l = 2^n
126122
}
127123

128124
// dec returns the previous integer position wrapping around queue q.
129125
func (q *Queue) dec(i int) int {
130126
l := len(q.rep)
131-
return (i - 1 + l) % l
127+
return (i - 1) & (l - 1) // requires l = 2^n
132128
}
133129

134130
// Front returns the first element of queue q or nil.

0 commit comments

Comments
 (0)