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