Skip to content

Commit 00e46a5

Browse files
committed
socket: amortise cost of querying OS time counter
#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 #142.
1 parent 32b18f7 commit 00e46a5

File tree

3 files changed

+97
-1
lines changed

3 files changed

+97
-1
lines changed

coarse_time.go

Lines changed: 62 additions & 0 deletions
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+
// coarseTime 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 coarseTime 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+
// coarseTime is safe for concurrent use.
20+
type coarseTime 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 *coarseTime) 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 *coarseTime) Close() {
35+
t.once.Do(func() {
36+
close(t.stop)
37+
})
38+
}
39+
40+
// newCoarseTime returns a coarseTime configured to update at granularity.
41+
func newCoarseTime(granularity time.Duration) *coarseTime {
42+
t := &coarseTime{
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

Lines changed: 23 additions & 0 deletions
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 TestCoarseTime(t *testing.T) {
9+
t.Parallel()
10+
11+
const granularity = 50 * time.Millisecond
12+
13+
ct := newCoarseTime(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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ 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 coarseTime).
44+
var coarseTime *coarseTime
45+
46+
func init() {
47+
coarseTime = newCoarseTime(25 * time.Millisecond)
48+
}
49+
3950
// ---------------------------------------------------------------------------
4051
// Mongo server encapsulation.
4152

@@ -293,7 +304,7 @@ func (server *mongoServer) close(waitForIdle bool) {
293304
func (server *mongoServer) RecycleSocket(socket *mongoSocket) {
294305
server.Lock()
295306
if !server.closed {
296-
socket.lastTimeUsed = time.Now()
307+
socket.lastTimeUsed = coarseTime.Now() // A rough approximation of the current time - see courseTime
297308
server.unusedSockets = append(server.unusedSockets, socket)
298309
}
299310
// If anybody is waiting for a connection, they should try now.

0 commit comments

Comments
 (0)