@@ -79,16 +79,15 @@ func (NilEWMA) Update(n int64) {}
79
79
type StandardEWMA struct {
80
80
uncounted int64 // /!\ this should be the first member to ensure 64-bit alignment
81
81
alpha float64
82
- rate float64
83
- init bool
82
+ rate uint64
83
+ init uint32
84
84
mutex sync.Mutex
85
85
}
86
86
87
87
// Rate returns the moving average rate of events per second.
88
88
func (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
92
91
}
93
92
94
93
// Snapshot returns a read-only copy of the EWMA.
@@ -99,17 +98,38 @@ func (a *StandardEWMA) Snapshot() EWMA {
99
98
// Tick ticks the clock to update the moving average. It assumes it is called
100
99
// every five seconds.
101
100
func (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 {
102
123
count := atomic .LoadInt64 (& a .uncounted )
103
124
atomic .AddInt64 (& a .uncounted , - count )
104
125
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 ))
113
133
}
114
134
115
135
// Update adds n uncounted events.
0 commit comments