@@ -79,16 +79,15 @@ func (NilEWMA) Update(n int64) {}
7979type StandardEWMA struct {
8080 uncounted int64 // /!\ this should be the first member to ensure 64-bit alignment
8181 alpha float64
82- rate float64
83- init bool
82+ rate uint64
83+ init uint32
8484 mutex sync.Mutex
8585}
8686
8787// Rate returns the moving average rate of events per second.
8888func (a * StandardEWMA ) Rate () float64 {
89- a .mutex .Lock ()
90- defer a .mutex .Unlock ()
91- return a .rate * float64 (1e9 )
89+ currentRate := math .Float64frombits (atomic .LoadUint64 (& a .rate )) * float64 (1e9 )
90+ return currentRate
9291}
9392
9493// Snapshot returns a read-only copy of the EWMA.
@@ -99,17 +98,38 @@ func (a *StandardEWMA) Snapshot() EWMA {
9998// Tick ticks the clock to update the moving average. It assumes it is called
10099// every five seconds.
101100func (a * StandardEWMA ) Tick () {
101+ // Optimization to avoid mutex locking in the hot-path.
102+ if atomic .LoadUint32 (& a .init ) == 1 {
103+ a .updateRate (a .fetchInstantRate ())
104+ } else {
105+ // Slow-path: this is only needed on the first Tick() and preserves transactional updating
106+ // of init and rate in the else block. The first conditional is needed below because
107+ // a different thread could have set a.init = 1 between the time of the first atomic load and when
108+ // the lock was acquired.
109+ a .mutex .Lock ()
110+ if atomic .LoadUint32 (& a .init ) == 1 {
111+ // The fetchInstantRate() uses atomic loading, which is unecessary in this critical section
112+ // but again, this section is only invoked on the first successful Tick() operation.
113+ a .updateRate (a .fetchInstantRate ())
114+ } else {
115+ atomic .StoreUint32 (& a .init , 1 )
116+ atomic .StoreUint64 (& a .rate , math .Float64bits (a .fetchInstantRate ()))
117+ }
118+ a .mutex .Unlock ()
119+ }
120+ }
121+
122+ func (a * StandardEWMA ) fetchInstantRate () float64 {
102123 count := atomic .LoadInt64 (& a .uncounted )
103124 atomic .AddInt64 (& a .uncounted , - count )
104125 instantRate := float64 (count ) / float64 (5e9 )
105- a .mutex .Lock ()
106- defer a .mutex .Unlock ()
107- if a .init {
108- a .rate += a .alpha * (instantRate - a .rate )
109- } else {
110- a .init = true
111- a .rate = instantRate
112- }
126+ return instantRate
127+ }
128+
129+ func (a * StandardEWMA ) updateRate (instantRate float64 ) {
130+ currentRate := math .Float64frombits (atomic .LoadUint64 (& a .rate ))
131+ currentRate += a .alpha * (instantRate - currentRate )
132+ atomic .StoreUint64 (& a .rate , math .Float64bits (currentRate ))
113133}
114134
115135// Update adds n uncounted events.
0 commit comments