pkg/timecache: implement a time cache

This commit is contained in:
Leo Balduf 2017-09-29 00:49:38 +02:00
parent 8300621799
commit 55b57549a6
2 changed files with 275 additions and 0 deletions

127
pkg/timecache/timecache.go Normal file
View file

@ -0,0 +1,127 @@
// Package timecache provides a cache for the system clock, to avoid calls to
// time.Now().
// The time is stored as one int64 which holds the number of nanoseconds since
// the Unix Epoch. The value is accessed using atomic primitives, without
// locking.
// The package runs a global singleton TimeCache that is is updated every
// second.
package timecache
import (
"sync"
"sync/atomic"
"time"
)
// t is the global TimeCache.
var t *TimeCache
func init() {
t = &TimeCache{
clock: time.Now().UnixNano(),
closed: make(chan struct{}),
running: make(chan struct{}),
}
go t.Run(1 * time.Second)
}
// A TimeCache is a cache for the current system time.
// The cached time has nanosecond precision.
type TimeCache struct {
// clock saves the current time's nanoseconds since the Epoch.
// Must be accessed atomically.
clock int64
closed chan struct{}
running chan struct{}
m sync.Mutex
}
// New returns a new TimeCache instance.
// The TimeCache must be started to update the time.
func New() *TimeCache {
return &TimeCache{
clock: time.Now().UnixNano(),
closed: make(chan struct{}),
running: make(chan struct{}),
}
}
// Run runs the TimeCache, updating the cached clock value once every interval
// and blocks until Stop is called.
func (t *TimeCache) Run(interval time.Duration) {
t.m.Lock()
select {
case <-t.running:
panic("Run called multiple times")
default:
}
close(t.running)
t.m.Unlock()
tick := time.NewTicker(interval)
defer tick.Stop()
for {
select {
case <-t.closed:
tick.Stop()
return
case now := <-tick.C:
atomic.StoreInt64(&t.clock, now.UnixNano())
}
}
}
// Stop stops the TimeCache.
// The cached time remains valid but will not be updated anymore.
// A TimeCache can not be restarted. Construct a new one instead.
// Calling Stop again is a no-op.
func (t *TimeCache) Stop() {
t.m.Lock()
defer t.m.Unlock()
select {
case <-t.closed:
return
default:
}
close(t.closed)
}
// Now returns the cached time as a time.Time value.
func (t *TimeCache) Now() time.Time {
return time.Unix(0, atomic.LoadInt64(&t.clock))
}
// NowUnixNano returns the cached time as nanoseconds since the Unix Epoch.
func (t *TimeCache) NowUnixNano() int64 {
return atomic.LoadInt64(&t.clock)
}
// NowUnix returns the cached time as seconds since the Unix Epoch.
func (t *TimeCache) NowUnix() int64 {
// Adopted from time.Unix
nsec := atomic.LoadInt64(&t.clock)
sec := nsec / 1e9
nsec -= sec * 1e9
if nsec < 0 {
sec--
}
return sec
}
// Now calls Now on the global TimeCache instance.
func Now() time.Time {
return t.Now()
}
// NowUnixNano calls NowUnixNano on the global TimeCache instance.
func NowUnixNano() int64 {
return t.NowUnixNano()
}
// NowUnix calls NowUnix on the global TimeCache instance.
func NowUnix() int64 {
return t.NowUnix()
}

View file

@ -0,0 +1,148 @@
package timecache
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestNew(t *testing.T) {
c := New()
require.NotNil(t, c)
now := c.Now()
require.False(t, now.IsZero())
nsec := c.NowUnixNano()
require.NotEqual(t, 0, nsec)
sec := c.NowUnix()
require.NotEqual(t, 0, sec)
}
func TestRunStop(t *testing.T) {
c := New()
require.NotNil(t, c)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
c.Run(1 * time.Second)
}()
c.Stop()
wg.Wait()
}
func TestMultipleStop(t *testing.T) {
c := New()
require.NotNil(t, c)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
c.Run(1 * time.Second)
}()
c.Stop()
c.Stop()
wg.Wait()
}
func doBenchmark(b *testing.B, f func(tc *TimeCache) func(*testing.PB)) {
tc := New()
require.NotNil(b, tc)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
tc.Run(1 * time.Second)
}()
b.RunParallel(f(tc))
tc.Stop()
wg.Wait()
}
func BenchmarkNow(b *testing.B) {
doBenchmark(b, func(tc *TimeCache) func(pb *testing.PB) {
return func(pb *testing.PB) {
var now time.Time
for pb.Next() {
now = tc.Now()
}
_ = now
}
})
}
func BenchmarkNowUnix(b *testing.B) {
doBenchmark(b, func(tc *TimeCache) func(pb *testing.PB) {
return func(pb *testing.PB) {
var now int64
for pb.Next() {
now = tc.NowUnix()
}
_ = now
}
})
}
func BenchmarkNowUnixNano(b *testing.B) {
doBenchmark(b, func(tc *TimeCache) func(pb *testing.PB) {
return func(pb *testing.PB) {
var now int64
for pb.Next() {
now = tc.NowUnixNano()
}
_ = now
}
})
}
func BenchmarkNowGlobal(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var now time.Time
for pb.Next() {
now = Now()
}
_ = now
})
}
func BenchmarkNowUnixGlobal(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var now int64
for pb.Next() {
now = NowUnix()
}
_ = now
})
}
func BenchmarkNowUnixNanoGlobal(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var now int64
for pb.Next() {
now = NowUnixNano()
}
_ = now
})
}
func BenchmarkTimeNow(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var now time.Time
for pb.Next() {
now = time.Now()
}
_ = now
})
}