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
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CounterFloat64 holds a float64 value that can be incremented and decremented.
|
// CounterFloat64 holds a float64 value that can be incremented and decremented.
|
||||||
@ -38,13 +39,13 @@ func NewCounterFloat64() CounterFloat64 {
|
|||||||
if !Enabled {
|
if !Enabled {
|
||||||
return NilCounterFloat64{}
|
return NilCounterFloat64{}
|
||||||
}
|
}
|
||||||
return &StandardCounterFloat64{count: 0.0}
|
return &StandardCounterFloat64{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCounterFloat64Forced constructs a new StandardCounterFloat64 and returns it no matter if
|
// NewCounterFloat64Forced constructs a new StandardCounterFloat64 and returns it no matter if
|
||||||
// the global switch is enabled or not.
|
// the global switch is enabled or not.
|
||||||
func NewCounterFloat64Forced() CounterFloat64 {
|
func NewCounterFloat64Forced() CounterFloat64 {
|
||||||
return &StandardCounterFloat64{count: 0.0}
|
return &StandardCounterFloat64{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRegisteredCounterFloat64 constructs and registers a new StandardCounterFloat64.
|
// NewRegisteredCounterFloat64 constructs and registers a new StandardCounterFloat64.
|
||||||
@ -113,41 +114,42 @@ func (NilCounterFloat64) Inc(i float64) {}
|
|||||||
func (NilCounterFloat64) Snapshot() CounterFloat64 { return NilCounterFloat64{} }
|
func (NilCounterFloat64) Snapshot() CounterFloat64 { return NilCounterFloat64{} }
|
||||||
|
|
||||||
// StandardCounterFloat64 is the standard implementation of a CounterFloat64 and uses the
|
// 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 {
|
type StandardCounterFloat64 struct {
|
||||||
mutex sync.Mutex
|
floatBits atomic.Uint64
|
||||||
count float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear sets the counter to zero.
|
// Clear sets the counter to zero.
|
||||||
func (c *StandardCounterFloat64) Clear() {
|
func (c *StandardCounterFloat64) Clear() {
|
||||||
c.mutex.Lock()
|
c.floatBits.Store(0)
|
||||||
defer c.mutex.Unlock()
|
|
||||||
c.count = 0.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count returns the current value.
|
// Count returns the current value.
|
||||||
func (c *StandardCounterFloat64) Count() float64 {
|
func (c *StandardCounterFloat64) Count() float64 {
|
||||||
c.mutex.Lock()
|
return math.Float64frombits(c.floatBits.Load())
|
||||||
defer c.mutex.Unlock()
|
|
||||||
return c.count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dec decrements the counter by the given amount.
|
// Dec decrements the counter by the given amount.
|
||||||
func (c *StandardCounterFloat64) Dec(v float64) {
|
func (c *StandardCounterFloat64) Dec(v float64) {
|
||||||
c.mutex.Lock()
|
atomicAddFloat(&c.floatBits, -v)
|
||||||
defer c.mutex.Unlock()
|
|
||||||
c.count -= v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inc increments the counter by the given amount.
|
// Inc increments the counter by the given amount.
|
||||||
func (c *StandardCounterFloat64) Inc(v float64) {
|
func (c *StandardCounterFloat64) Inc(v float64) {
|
||||||
c.mutex.Lock()
|
atomicAddFloat(&c.floatBits, v)
|
||||||
defer c.mutex.Unlock()
|
|
||||||
c.count += v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns a read-only copy of the counter.
|
// Snapshot returns a read-only copy of the counter.
|
||||||
func (c *StandardCounterFloat64) Snapshot() CounterFloat64 {
|
func (c *StandardCounterFloat64) Snapshot() CounterFloat64 {
|
||||||
return CounterFloat64Snapshot(c.Count())
|
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
|
package metrics
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func BenchmarkCounterFloat64(b *testing.B) {
|
func BenchmarkCounterFloat64(b *testing.B) {
|
||||||
c := NewCounterFloat64()
|
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) {
|
func TestCounterFloat64Clear(t *testing.T) {
|
||||||
c := NewCounterFloat64()
|
c := NewCounterFloat64()
|
||||||
c.Inc(1.0)
|
c.Inc(1.0)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
// GaugeFloat64s hold a float64 value that can be set arbitrarily.
|
// GaugeFloat64s hold a float64 value that can be set arbitrarily.
|
||||||
type GaugeFloat64 interface {
|
type GaugeFloat64 interface {
|
||||||
@ -23,9 +26,7 @@ func NewGaugeFloat64() GaugeFloat64 {
|
|||||||
if !Enabled {
|
if !Enabled {
|
||||||
return NilGaugeFloat64{}
|
return NilGaugeFloat64{}
|
||||||
}
|
}
|
||||||
return &StandardGaugeFloat64{
|
return &StandardGaugeFloat64{}
|
||||||
value: 0.0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRegisteredGaugeFloat64 constructs and registers a new StandardGaugeFloat64.
|
// NewRegisteredGaugeFloat64 constructs and registers a new StandardGaugeFloat64.
|
||||||
@ -83,10 +84,9 @@ func (NilGaugeFloat64) Update(v float64) {}
|
|||||||
func (NilGaugeFloat64) Value() float64 { return 0.0 }
|
func (NilGaugeFloat64) Value() float64 { return 0.0 }
|
||||||
|
|
||||||
// StandardGaugeFloat64 is the standard implementation of a GaugeFloat64 and uses
|
// 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 {
|
type StandardGaugeFloat64 struct {
|
||||||
mutex sync.Mutex
|
floatBits atomic.Uint64
|
||||||
value float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns a read-only copy of the gauge.
|
// Snapshot returns a read-only copy of the gauge.
|
||||||
@ -96,16 +96,12 @@ func (g *StandardGaugeFloat64) Snapshot() GaugeFloat64 {
|
|||||||
|
|
||||||
// Update updates the gauge's value.
|
// Update updates the gauge's value.
|
||||||
func (g *StandardGaugeFloat64) Update(v float64) {
|
func (g *StandardGaugeFloat64) Update(v float64) {
|
||||||
g.mutex.Lock()
|
g.floatBits.Store(math.Float64bits(v))
|
||||||
defer g.mutex.Unlock()
|
|
||||||
g.value = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value returns the gauge's current value.
|
// Value returns the gauge's current value.
|
||||||
func (g *StandardGaugeFloat64) Value() float64 {
|
func (g *StandardGaugeFloat64) Value() float64 {
|
||||||
g.mutex.Lock()
|
return math.Float64frombits(g.floatBits.Load())
|
||||||
defer g.mutex.Unlock()
|
|
||||||
return g.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionalGaugeFloat64 returns value from given function
|
// FunctionalGaugeFloat64 returns value from given function
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func BenchmarkGaugeFloat64(b *testing.B) {
|
func BenchmarkGaugeFloat64(b *testing.B) {
|
||||||
g := NewGaugeFloat64()
|
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) {
|
func TestGaugeFloat64(t *testing.T) {
|
||||||
g := NewGaugeFloat64()
|
g := NewGaugeFloat64()
|
||||||
g.Update(47.0)
|
g.Update(47.0)
|
||||||
|
Loading…
Reference in New Issue
Block a user