Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

time-smoother #8

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/polygon-io/go-app-ticker-wall
go 1.16

require (
github.com/dterei/gotsc v0.0.0-20160722215413-e78f872945c6
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838
github.com/polygon-io/nanovgo v0.0.0-20210406222537-1c1e04bebee3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dterei/gotsc v0.0.0-20160722215413-e78f872945c6 h1:iD62k/20LzNdTc2bmrOTQ4xgRbI0uJvOxTkEAmd20eQ=
github.com/dterei/gotsc v0.0.0-20160722215413-e78f872945c6/go.mod h1:P4N3xGqi52atrdlMBXpsAGTqRnLgZ8uDhlkQ7HEYGgo=
github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d h1:o81yRlBATU4PRn97lydmsq8hTRNXI4wlR/VvUQhFRVY=
github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI=
Expand Down
111 changes: 111 additions & 0 deletions time_smoother/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package time_smoother

import (
"math"
"time"

"github.com/dterei/gotsc"
)

type TimeSmoother struct {
lastRdtsc uint64 // last cycle count
lastRdtscNanos uint64 // last cycle-synced time
lastSystemTime uint64 // last system time
rdtscTime uint64 // time to call rdtsc

clockSpeed float64
clockSpeedDecayRate float64

adjustedTimeRate float64 // nanos per clock

syncInterval uint64

stop chan bool

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If stop is only acting as a signal, an empty struct would be more memory efficient to use instead of a bool.

Here's a blog post by Dave Cheney explaining why.

}

func (t *TimeSmoother) Stop() {
t.stop <- true
}

func (t *TimeSmoother) GetTime() time.Time {
tnow := gotsc.BenchStart()

// delta in cycles
tDelta := float64(tnow - t.lastRdtsc)

nanosNow := int64(tDelta*t.adjustedTimeRate) + int64(t.lastRdtscNanos)

return time.Unix(0, nanosNow)
}

//
func (t *TimeSmoother) sync(systemTime, rdtsc uint64) {
elapsedCycles := rdtsc - t.lastRdtsc
elapsedNanos := int64(systemTime) - int64(t.lastSystemTime)

nanosNow := int64(float64(elapsedCycles)*t.adjustedTimeRate) + int64(t.lastRdtscNanos) // current extrapolated time

newClock := float64(elapsedNanos) / float64(elapsedCycles)
t.clockSpeed = t.clockSpeedDecayRate*t.clockSpeed + (1-t.clockSpeedDecayRate)*newClock

targetSystemTime := int64(systemTime + t.syncInterval)
timeRate := float64(targetSystemTime-nanosNow) / t.clockSpeed
if timeRate < 0 {
timeRate = 0.0
}

t.adjustedTimeRate = timeRate
t.lastSystemTime = systemTime
t.lastRdtsc = rdtsc
t.lastRdtscNanos = uint64(nanosNow)
}

func (t *TimeSmoother) daemon() {
ticker := time.NewTicker(time.Duration(t.syncInterval))
for {
select {
case <-ticker.C:
systemTime := time.Now().UnixNano()
rdtsc := gotsc.BenchStart() - t.rdtscTime
t.sync(uint64(systemTime), rdtsc)
case <-t.stop:
return
}
}
}

// gets an initial estimate of the system clock speed
func (t *TimeSmoother) initClock() {
sys1 := time.Now()
rdtsc1 := gotsc.BenchStart()
time.Sleep(time.Second)
rdtsc2 := gotsc.BenchEnd()
sysDelta := time.Since(sys1)

cycles := rdtsc2 - rdtsc1 - 2*t.rdtscTime
elapsedTime := sysDelta.Nanoseconds()

clockSpeed := float64(elapsedTime) / float64(cycles)
t.clockSpeed = clockSpeed
}

func NewTimeSmoother() *TimeSmoother {
smoother := TimeSmoother{}
smoother.rdtscTime = gotsc.TSCOverhead() / 2 // this function measures calling twice
smoother.lastSystemTime = uint64(time.Now().UnixNano())
smoother.lastRdtsc = gotsc.BenchStart() - smoother.rdtscTime
smoother.lastRdtscNanos = smoother.lastSystemTime

smoother.initClock()
smoother.adjustedTimeRate = smoother.clockSpeed

half_life := 10.0 // 10 second half life
smoother.clockSpeedDecayRate = math.Pow(2, -1.0/half_life)
smoother.syncInterval = 100_000_000 // 100 ms

smoother.stop = make(chan bool)

go smoother.daemon()

return &smoother
}