Skip to content

Commit 243904d

Browse files
authored
socket: amortise cost of querying OS time counter (globalsign#149)
globalsign#116 adds a much needed ability to shrink the connection pool, but requires tracking the last-used timestamp for each socket after every operation. Frequent calls to time.Now() in the hot-path reduced read throughput by ~6% and increased the latency (and variance) of socket operations as a whole. This PR adds a periodically updated time value to amortise the cost of the last- used bookkeeping, restoring the original throughput at the cost of approximate last-used values (configured to be ~25ms of potential skew). On some systems (currently including FreeBSD) querying the time counter also requires a syscall/context switch. Fixes globalsign#142.
1 parent 57bf42b commit 243904d

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

coarse_time.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package mgo
2+
3+
import (
4+
"sync"
5+
"sync/atomic"
6+
"time"
7+
)
8+
9+
// coarseTimeProvider provides a periodically updated (approximate) time value to
10+
// amortise the cost of frequent calls to time.Now.
11+
//
12+
// A read throughput increase of ~6% was measured when using coarseTimeProvider with the
13+
// high-precision event timer (HPET) on FreeBSD 11.1 and Go 1.10.1 after merging
14+
// #116.
15+
//
16+
// Calling Now returns a time.Time that is updated at the configured interval,
17+
// however due to scheduling the value may be marginally older than expected.
18+
//
19+
// coarseTimeProvider is safe for concurrent use.
20+
type coarseTimeProvider struct {
21+
once sync.Once
22+
stop chan struct{}
23+
last atomic.Value
24+
}
25+
26+
// Now returns the most recently acquired time.Time value.
27+
func (t *coarseTimeProvider) Now() time.Time {
28+
return t.last.Load().(time.Time)
29+
}
30+
31+
// Close stops the periodic update of t.
32+
//
33+
// Any subsequent calls to Now will return the same value forever.
34+
func (t *coarseTimeProvider) Close() {
35+
t.once.Do(func() {
36+
close(t.stop)
37+
})
38+
}
39+
40+
// newcoarseTimeProvider returns a coarseTimeProvider configured to update at granularity.
41+
func newcoarseTimeProvider(granularity time.Duration) *coarseTimeProvider {
42+
t := &coarseTimeProvider{
43+
stop: make(chan struct{}),
44+
}
45+
46+
t.last.Store(time.Now())
47+
48+
go func() {
49+
ticker := time.NewTicker(granularity)
50+
for {
51+
select {
52+
case <-t.stop:
53+
ticker.Stop()
54+
return
55+
case <-ticker.C:
56+
t.last.Store(time.Now())
57+
}
58+
}
59+
}()
60+
61+
return t
62+
}

coarse_time_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package mgo
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestCoarseTimeProvider(t *testing.T) {
9+
t.Parallel()
10+
11+
const granularity = 50 * time.Millisecond
12+
13+
ct := newcoarseTimeProvider(granularity)
14+
defer ct.Close()
15+
16+
start := ct.Now().Unix()
17+
time.Sleep(time.Second)
18+
19+
got := ct.Now().Unix()
20+
if got <= start {
21+
t.Fatalf("got %d, expected at least %d", got, start)
22+
}
23+
}

server.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ import (
3636
"github.com/globalsign/mgo/bson"
3737
)
3838

39+
// coarseTime is used to amortise the cost of querying the timecounter (possibly
40+
// incurring a syscall too) when setting a socket.lastTimeUsed value which
41+
// happens frequently in the hot-path.
42+
//
43+
// The lastTimeUsed value may be skewed by at least 25ms (see
44+
// coarseTimeProvider).
45+
var coarseTime *coarseTimeProvider
46+
47+
func init() {
48+
coarseTime = newcoarseTimeProvider(25 * time.Millisecond)
49+
}
50+
3951
// ---------------------------------------------------------------------------
4052
// Mongo server encapsulation.
4153

@@ -293,7 +305,7 @@ func (server *mongoServer) close(waitForIdle bool) {
293305
func (server *mongoServer) RecycleSocket(socket *mongoSocket) {
294306
server.Lock()
295307
if !server.closed {
296-
socket.lastTimeUsed = time.Now()
308+
socket.lastTimeUsed = coarseTime.Now() // A rough approximation of the current time - see courseTime
297309
server.unusedSockets = append(server.unusedSockets, socket)
298310
}
299311
// If anybody is waiting for a connection, they should try now.

0 commit comments

Comments
 (0)