Commit 986768de authored by Josh Bleecher Snyder's avatar Josh Bleecher Snyder

context: lazily initialize cancelCtx done channel

This CL reduces allocations when a context
created with WithCancel either

(1) never has its Done channel used or
(2) gets cancelled before its Done channel is used

This is not uncommon. Many contexts are created
for tasks that end up not using them.

name                                                old time/op    new time/op    delta
ContextCancelTree/depth=1/Root=Background-8            112ns ± 2%      74ns ± 1%  -34.03%  (p=0.000 n=17+18)
ContextCancelTree/depth=1/Root=OpenCanceler-8          601ns ± 3%     544ns ± 1%   -9.56%  (p=0.000 n=20+20)
ContextCancelTree/depth=1/Root=ClosedCanceler-8        367ns ± 4%     257ns ± 1%  -30.01%  (p=0.000 n=20+20)
ContextCancelTree/depth=10/Root=Background-8          2.91µs ± 2%    2.87µs ± 0%   -1.38%  (p=0.000 n=20+18)
ContextCancelTree/depth=10/Root=OpenCanceler-8        4.36µs ± 2%    4.26µs ± 1%   -2.34%  (p=0.000 n=20+18)
ContextCancelTree/depth=10/Root=ClosedCanceler-8      2.02µs ± 2%    1.51µs ± 1%  -25.18%  (p=0.000 n=19+19)
ContextCancelTree/depth=100/Root=Background-8         30.5µs ± 6%    30.5µs ± 1%     ~     (p=0.941 n=20+20)
ContextCancelTree/depth=100/Root=OpenCanceler-8       39.8µs ± 1%    41.1µs ± 1%   +3.15%  (p=0.000 n=18+19)
ContextCancelTree/depth=100/Root=ClosedCanceler-8     17.8µs ± 1%    13.9µs ± 1%  -21.61%  (p=0.000 n=18+20)
ContextCancelTree/depth=1000/Root=Background-8         302µs ± 1%     313µs ± 0%   +3.62%  (p=0.000 n=20+18)
ContextCancelTree/depth=1000/Root=OpenCanceler-8       412µs ± 2%     427µs ± 1%   +3.55%  (p=0.000 n=18+19)
ContextCancelTree/depth=1000/Root=ClosedCanceler-8     178µs ± 1%     139µs ± 1%  -21.80%  (p=0.000 n=19+17)

name                                                old alloc/op   new alloc/op   delta
ContextCancelTree/depth=1/Root=Background-8             176B ± 0%       80B ± 0%  -54.55%  (p=0.000 n=20+20)
ContextCancelTree/depth=1/Root=OpenCanceler-8           544B ± 0%      448B ± 0%  -17.65%  (p=0.000 n=20+20)
ContextCancelTree/depth=1/Root=ClosedCanceler-8         352B ± 0%      160B ± 0%  -54.55%  (p=0.000 n=20+20)
ContextCancelTree/depth=10/Root=Background-8          3.49kB ± 0%    3.39kB ± 0%   -2.75%  (p=0.000 n=20+20)
ContextCancelTree/depth=10/Root=OpenCanceler-8        3.86kB ± 0%    3.76kB ± 0%   -2.49%  (p=0.000 n=20+20)
ContextCancelTree/depth=10/Root=ClosedCanceler-8      1.94kB ± 0%    0.88kB ± 0%  -54.55%  (p=0.000 n=20+20)
ContextCancelTree/depth=100/Root=Background-8         36.6kB ± 0%    36.5kB ± 0%   -0.26%  (p=0.000 n=20+20)
ContextCancelTree/depth=100/Root=OpenCanceler-8       37.0kB ± 0%    36.9kB ± 0%   -0.26%  (p=0.000 n=20+20)
ContextCancelTree/depth=100/Root=ClosedCanceler-8     17.8kB ± 0%     8.1kB ± 0%  -54.55%  (p=0.000 n=20+20)
ContextCancelTree/depth=1000/Root=Background-8         368kB ± 0%     368kB ± 0%   -0.03%  (p=0.000 n=20+20)
ContextCancelTree/depth=1000/Root=OpenCanceler-8       368kB ± 0%     368kB ± 0%   -0.03%  (p=0.000 n=20+20)
ContextCancelTree/depth=1000/Root=ClosedCanceler-8     176kB ± 0%      80kB ± 0%  -54.55%  (p=0.000 n=20+20)

name                                                old allocs/op  new allocs/op  delta
ContextCancelTree/depth=1/Root=Background-8             3.00 ± 0%      2.00 ± 0%  -33.33%  (p=0.000 n=20+20)
ContextCancelTree/depth=1/Root=OpenCanceler-8           8.00 ± 0%      7.00 ± 0%  -12.50%  (p=0.000 n=20+20)
ContextCancelTree/depth=1/Root=ClosedCanceler-8         6.00 ± 0%      4.00 ± 0%  -33.33%  (p=0.000 n=20+20)
ContextCancelTree/depth=10/Root=Background-8            48.0 ± 0%      47.0 ± 0%   -2.08%  (p=0.000 n=20+20)
ContextCancelTree/depth=10/Root=OpenCanceler-8          53.0 ± 0%      52.0 ± 0%   -1.89%  (p=0.000 n=20+20)
ContextCancelTree/depth=10/Root=ClosedCanceler-8        33.0 ± 0%      22.0 ± 0%  -33.33%  (p=0.000 n=20+20)
ContextCancelTree/depth=100/Root=Background-8            498 ± 0%       497 ± 0%   -0.20%  (p=0.000 n=20+20)
ContextCancelTree/depth=100/Root=OpenCanceler-8          503 ± 0%       502 ± 0%   -0.20%  (p=0.000 n=20+20)
ContextCancelTree/depth=100/Root=ClosedCanceler-8        303 ± 0%       202 ± 0%  -33.33%  (p=0.000 n=20+20)
ContextCancelTree/depth=1000/Root=Background-8         5.00k ± 0%     5.00k ± 0%   -0.02%  (p=0.000 n=20+20)
ContextCancelTree/depth=1000/Root=OpenCanceler-8       5.00k ± 0%     5.00k ± 0%   -0.02%  (p=0.000 n=20+20)
ContextCancelTree/depth=1000/Root=ClosedCanceler-8     3.00k ± 0%     2.00k ± 0%  -33.33%  (p=0.000 n=20+20)

Change-Id: Ibd7a0c3d5c847861cf1497f8fead34329413d26d
Reviewed-on: https://go-review.googlesource.com/34979
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: 's avatarSameer Ajmani <sameer@golang.org>
parent fd118b69
......@@ -234,10 +234,7 @@ func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
return cancelCtx{Context: parent}
}
// propagateCancel arranges for child to be canceled when parent is.
......@@ -306,20 +303,32 @@ type canceler interface {
Done() <-chan struct{}
}
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {
close(closedchan)
}
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
func (c *cancelCtx) Done() <-chan struct{} {
return c.done
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
func (c *cancelCtx) Err() error {
......@@ -344,7 +353,11 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) {
return // already canceled
}
c.err = err
close(c.done)
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
......
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