metrics: make gauge_float64 and counter_float64 lock free (#27025)
Makes the float-gauges lock-free name old time/op new time/op delta CounterFloat64Parallel-8 1.45µs ±10% 0.85µs ± 6% -41.65% (p=0.008 n=5+5) --------- Co-authored-by: Exca-DK <dev@DESKTOP-RI45P4J.localdomain> Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
parent
ab1a404b01
commit
b4dcd1a391
|
@ -1,7 +1,8 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// CounterFloat64 holds a float64 value that can be incremented and decremented.
|
||||
|
@ -38,13 +39,13 @@ func NewCounterFloat64() CounterFloat64 {
|
|||
if !Enabled {
|
||||
return NilCounterFloat64{}
|
||||
}
|
||||
return &StandardCounterFloat64{count: 0.0}
|
||||
return &StandardCounterFloat64{}
|
||||
}
|
||||
|
||||
// NewCounterFloat64Forced constructs a new StandardCounterFloat64 and returns it no matter if
|
||||
// the global switch is enabled or not.
|
||||
func NewCounterFloat64Forced() CounterFloat64 {
|
||||
return &StandardCounterFloat64{count: 0.0}
|
||||
return &StandardCounterFloat64{}
|
||||
}
|
||||
|
||||
// NewRegisteredCounterFloat64 constructs and registers a new StandardCounterFloat64.
|
||||
|
@ -113,41 +114,42 @@ func (NilCounterFloat64) Inc(i float64) {}
|
|||
func (NilCounterFloat64) Snapshot() CounterFloat64 { return NilCounterFloat64{} }
|
||||
|
||||
// StandardCounterFloat64 is the standard implementation of a CounterFloat64 and uses the
|
||||
// sync.Mutex package to manage a single float64 value.
|
||||
// atomic to manage a single float64 value.
|
||||
type StandardCounterFloat64 struct {
|
||||
mutex sync.Mutex
|
||||
count float64
|
||||
floatBits atomic.Uint64
|
||||
}
|
||||
|
||||
// Clear sets the counter to zero.
|
||||
func (c *StandardCounterFloat64) Clear() {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.count = 0.0
|
||||
c.floatBits.Store(0)
|
||||
}
|
||||
|
||||
// Count returns the current value.
|
||||
func (c *StandardCounterFloat64) Count() float64 {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
return c.count
|
||||
return math.Float64frombits(c.floatBits.Load())
|
||||
}
|
||||
|
||||
// Dec decrements the counter by the given amount.
|
||||
func (c *StandardCounterFloat64) Dec(v float64) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.count -= v
|
||||
atomicAddFloat(&c.floatBits, -v)
|
||||
}
|
||||
|
||||
// Inc increments the counter by the given amount.
|
||||
func (c *StandardCounterFloat64) Inc(v float64) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.count += v
|
||||
atomicAddFloat(&c.floatBits, v)
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the counter.
|
||||
func (c *StandardCounterFloat64) Snapshot() CounterFloat64 {
|
||||
return CounterFloat64Snapshot(c.Count())
|
||||
}
|
||||
|
||||
func atomicAddFloat(fbits *atomic.Uint64, v float64) {
|
||||
for {
|
||||
loadedBits := fbits.Load()
|
||||
newBits := math.Float64bits(math.Float64frombits(loadedBits) + v)
|
||||
if fbits.CompareAndSwap(loadedBits, newBits) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package metrics
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkCounterFloat64(b *testing.B) {
|
||||
c := NewCounterFloat64()
|
||||
|
@ -10,6 +13,25 @@ func BenchmarkCounterFloat64(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkCounterFloat64Parallel(b *testing.B) {
|
||||
c := NewCounterFloat64()
|
||||
b.ResetTimer()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Inc(1.0)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if have, want := c.Count(), 10.0*float64(b.N); have != want {
|
||||
b.Fatalf("have %f want %f", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Clear(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
c.Inc(1.0)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package metrics
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// GaugeFloat64s hold a float64 value that can be set arbitrarily.
|
||||
type GaugeFloat64 interface {
|
||||
|
@ -23,9 +26,7 @@ func NewGaugeFloat64() GaugeFloat64 {
|
|||
if !Enabled {
|
||||
return NilGaugeFloat64{}
|
||||
}
|
||||
return &StandardGaugeFloat64{
|
||||
value: 0.0,
|
||||
}
|
||||
return &StandardGaugeFloat64{}
|
||||
}
|
||||
|
||||
// NewRegisteredGaugeFloat64 constructs and registers a new StandardGaugeFloat64.
|
||||
|
@ -83,10 +84,9 @@ func (NilGaugeFloat64) Update(v float64) {}
|
|||
func (NilGaugeFloat64) Value() float64 { return 0.0 }
|
||||
|
||||
// StandardGaugeFloat64 is the standard implementation of a GaugeFloat64 and uses
|
||||
// sync.Mutex to manage a single float64 value.
|
||||
// atomic to manage a single float64 value.
|
||||
type StandardGaugeFloat64 struct {
|
||||
mutex sync.Mutex
|
||||
value float64
|
||||
floatBits atomic.Uint64
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the gauge.
|
||||
|
@ -96,16 +96,12 @@ func (g *StandardGaugeFloat64) Snapshot() GaugeFloat64 {
|
|||
|
||||
// Update updates the gauge's value.
|
||||
func (g *StandardGaugeFloat64) Update(v float64) {
|
||||
g.mutex.Lock()
|
||||
defer g.mutex.Unlock()
|
||||
g.value = v
|
||||
g.floatBits.Store(math.Float64bits(v))
|
||||
}
|
||||
|
||||
// Value returns the gauge's current value.
|
||||
func (g *StandardGaugeFloat64) Value() float64 {
|
||||
g.mutex.Lock()
|
||||
defer g.mutex.Unlock()
|
||||
return g.value
|
||||
return math.Float64frombits(g.floatBits.Load())
|
||||
}
|
||||
|
||||
// FunctionalGaugeFloat64 returns value from given function
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package metrics
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkGaugeFloat64(b *testing.B) {
|
||||
g := NewGaugeFloat64()
|
||||
|
@ -10,6 +13,24 @@ func BenchmarkGaugeFloat64(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkGaugeFloat64Parallel(b *testing.B) {
|
||||
c := NewGaugeFloat64()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Update(float64(i))
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if have, want := c.Value(), float64(b.N-1); have != want {
|
||||
b.Fatalf("have %f want %f", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGaugeFloat64(t *testing.T) {
|
||||
g := NewGaugeFloat64()
|
||||
g.Update(47.0)
|
||||
|
|
Loading…
Reference in New Issue