Selaa lähdekoodia

speed up registry Each method

Creating a map entry with a string type as key leads to a copy of that
string in Go. Iterating via Each creates a lot of these entries,
especially while reporting lots of values to a remote metrics system.

Our use case is modelled with HugeRegistry and the below benchmarks show
improvements there to to the removed memory allocation.

```
name                  old time/op    new time/op    delta
Counter-4               9.83ns ± 0%   10.03ns ± 1%   +2.03%          (p=0.016 n=4+5)
GuageFloat64-4          62.7ns ± 4%    60.6ns ± 1%   -3.38%          (p=0.008 n=5+5)
Histogram-4              117ns ± 3%     129ns ± 4%  +10.26%          (p=0.008 n=5+5)
Registry-4               760ns ± 2%     232ns ± 2%  -69.52%          (p=0.008 n=5+5)
HugeRegistry-4          2.58ms ± 3%    0.82ms ± 1%  -68.10%          (p=0.008 n=5+5)
UniformSample257-4       108ns ± 2%     123ns ± 5%  +13.10%          (p=0.008 n=5+5)
UniformSample514-4       106ns ± 2%     118ns ± 5%  +10.90%          (p=0.008 n=5+5)
UniformSample1028-4      106ns ± 1%     119ns ± 2%  +12.43%          (p=0.008 n=5+5)

name                  old alloc/op   new alloc/op   delta
Registry-4                336B ± 0%       32B ± 0%  -90.48%          (p=0.008 n=5+5)
HugeRegistry-4           598kB ± 0%     328kB ± 0%     ~             (p=0.079 n=4+5)

name                  old allocs/op  new allocs/op  delta
Registry-4                2.00 ± 0%      1.00 ± 0%  -50.00%          (p=0.008 n=5+5)
HugeRegistry-4            5.00 ± 0%      3.00 ± 0%  -40.00%          (p=0.008 n=5+5)
```
Ingo Oeser 7 vuotta sitten
vanhempi
commit
4123bd99cb
2 muutettua tiedostoa jossa 31 lisäystä ja 6 poistoa
  1. 15 6
      registry.go
  2. 16 0
      registry_test.go

+ 15 - 6
registry.go

@@ -64,8 +64,10 @@ func NewRegistry() Registry {
 
 // Call the given function for each registered metric.
 func (r *StandardRegistry) Each(f func(string, interface{})) {
-	for name, i := range r.registered() {
-		f(name, i)
+	metrics := r.registered()
+	for i := range metrics {
+		kv := &metrics[i]
+		f(kv.name, kv.value)
 	}
 }
 
@@ -211,16 +213,23 @@ func (r *StandardRegistry) register(name string, i interface{}) error {
 	return nil
 }
 
-func (r *StandardRegistry) registered() map[string]interface{} {
+type metricKV struct {
+	name  string
+	value interface{}
+}
+
+func (r *StandardRegistry) registered() []metricKV {
+	metrics := make([]metricKV, 0, len(r.metrics))
 	r.mutex.Lock()
 	defer r.mutex.Unlock()
-	metrics := make(map[string]interface{}, len(r.metrics))
 	for name, i := range r.metrics {
-		metrics[name] = i
+		metrics = append(metrics, metricKV{
+			name:  name,
+			value: i,
+		})
 	}
 	return metrics
 }
-
 func (r *StandardRegistry) stop(name string) {
 	if i, ok := r.metrics[name]; ok {
 		if s, ok := i.(Stoppable); ok {

+ 16 - 0
registry_test.go

@@ -1,6 +1,7 @@
 package metrics
 
 import (
+	"fmt"
 	"sync"
 	"testing"
 )
@@ -14,6 +15,21 @@ func BenchmarkRegistry(b *testing.B) {
 	}
 }
 
+func BenchmarkHugeRegistry(b *testing.B) {
+	r := NewRegistry()
+	for i := 0; i < 10000; i++ {
+		r.Register(fmt.Sprintf("foo%07d", i), NewCounter())
+	}
+	v := make([]string, 10000)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		v := v[:0]
+		r.Each(func(k string, _ interface{}) {
+			v = append(v, k)
+		})
+	}
+}
+
 func BenchmarkRegistryParallel(b *testing.B) {
 	r := NewRegistry()
 	b.ResetTimer()