Commit 8eb8b40a authored by Keith Randall's avatar Keith Randall

runtime: use doubly-linked lists for channel send/recv queues.

Avoids a potential O(n^2) performance problem when dequeueing
from very popular channels.

benchmark                old ns/op     new ns/op     delta
BenchmarkChanPopular     2563782       627201        -75.54%

Change-Id: I231aaeafea0ecd93d27b268a0b2128530df3ddd6
Reviewed-on: https://go-review.googlesource.com/1200Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent 006ceb2f
...@@ -336,7 +336,7 @@ selecttype(int32 size) ...@@ -336,7 +336,7 @@ selecttype(int32 size)
sudog = nod(OTSTRUCT, N, N); sudog = nod(OTSTRUCT, N, N);
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("g")), typenod(ptrto(types[TUINT8])))); sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("g")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("selectdone")), typenod(ptrto(types[TUINT8])))); sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("selectdone")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("link")), typenod(ptrto(types[TUINT8])))); sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("next")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("prev")), typenod(ptrto(types[TUINT8])))); sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("prev")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("elem")), typenod(ptrto(types[TUINT8])))); sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("elem")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("releasetime")), typenod(types[TUINT64]))); sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("releasetime")), typenod(types[TUINT64])));
......
...@@ -614,12 +614,15 @@ func reflect_chancap(c *hchan) int { ...@@ -614,12 +614,15 @@ func reflect_chancap(c *hchan) int {
func (q *waitq) enqueue(sgp *sudog) { func (q *waitq) enqueue(sgp *sudog) {
sgp.next = nil sgp.next = nil
if q.first == nil { x := q.last
if x == nil {
sgp.prev = nil
q.first = sgp q.first = sgp
q.last = sgp q.last = sgp
return return
} }
q.last.next = sgp sgp.prev = x
x.next = sgp
q.last = sgp q.last = sgp
} }
...@@ -629,10 +632,14 @@ func (q *waitq) dequeue() *sudog { ...@@ -629,10 +632,14 @@ func (q *waitq) dequeue() *sudog {
if sgp == nil { if sgp == nil {
return nil return nil
} }
q.first = sgp.next y := sgp.next
sgp.next = nil if y == nil {
if q.last == sgp { q.first = nil
q.last = nil q.last = nil
} else {
y.prev = nil
q.first = y
sgp.next = nil // mark as removed (see dequeueSudog)
} }
// if sgp participates in a select and is already signaled, ignore it // if sgp participates in a select and is already signaled, ignore it
......
...@@ -818,3 +818,26 @@ func BenchmarkChanSem(b *testing.B) { ...@@ -818,3 +818,26 @@ func BenchmarkChanSem(b *testing.B) {
} }
}) })
} }
func BenchmarkChanPopular(b *testing.B) {
const n = 1000
c := make(chan bool)
var a []chan bool
for j := 0; j < n; j++ {
d := make(chan bool)
a = append(a, d)
go func() {
for i := 0; i < b.N; i++ {
select {
case <-c:
case <-d:
}
}
}()
}
for i := 0; i < b.N; i++ {
for _, d := range a {
d <- true
}
}
}
...@@ -389,6 +389,7 @@ loop: ...@@ -389,6 +389,7 @@ loop:
k.releasetime = sglist.releasetime k.releasetime = sglist.releasetime
} }
if sg == sglist { if sg == sglist {
// sg has already been dequeued by the G that woke us up.
cas = k cas = k
} else { } else {
c = k._chan c = k._chan
...@@ -624,23 +625,36 @@ func reflect_rselect(cases []runtimeSelect) (chosen int, recvOK bool) { ...@@ -624,23 +625,36 @@ func reflect_rselect(cases []runtimeSelect) (chosen int, recvOK bool) {
return return
} }
func (q *waitq) dequeueSudoG(s *sudog) { func (q *waitq) dequeueSudoG(sgp *sudog) {
var prevsgp *sudog x := sgp.prev
l := &q.first y := sgp.next
for { if x != nil {
sgp := *l if y != nil {
if sgp == nil { // middle of queue
x.next = y
y.prev = x
sgp.next = nil
sgp.prev = nil
return return
} }
if sgp == s { // end of queue
*l = sgp.next x.next = nil
if q.last == sgp { q.last = x
q.last = prevsgp sgp.prev = nil
} return
s.next = nil }
return if y != nil {
} // start of queue
l = &sgp.next y.prev = nil
prevsgp = sgp q.first = y
sgp.next = nil
return
}
// x==y==nil. Either sgp is the only element in the queue,
// or it has already been removed. Use q.first to disambiguate.
if q.first == sgp {
q.first = nil
q.last = nil
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment