Skip to content

Commit ee1078a

Browse files
committed
Move registry hashing to xxhash
This is a much stronger hash function than fnv64a and comparably fast (with super-fast assembly implementation for amd64). Performance is not critical here anyway. The old fnv64a is kept for vectors, where collision detection is in place and the weakness of the hashing doesn't matter that much. I implemented a vector version with xxhash and found that xxhash is slower in all cases except very very high cardinality (where it is only slightly faster). Also, ``xxhash.New`` comes with an allocation of 80 bytes. Thus, to keep vectors alloc-free, we needed to add a `sync.Pool`, which would have an additional performance overhead. Signed-off-by: beorn7 <[email protected]>
1 parent c2e3855 commit ee1078a

File tree

5 files changed

+27
-19
lines changed

5 files changed

+27
-19
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module github.com/prometheus/client_golang
22

33
require (
44
github.com/beorn7/perks v1.0.1
5+
github.com/cespare/xxhash/v2 v2.1.0
56
github.com/golang/protobuf v1.3.2
67
github.com/json-iterator/go v1.1.7
78
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
88
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
99
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1010
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
11+
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
12+
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
1113
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1214
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1315
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

prometheus/desc.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"sort"
2020
"strings"
2121

22+
"github.com/cespare/xxhash/v2"
2223
"github.com/golang/protobuf/proto"
2324
"github.com/prometheus/common/model"
2425

@@ -126,24 +127,24 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
126127
return d
127128
}
128129

129-
vh := hashNew()
130+
xxh := xxhash.New()
130131
for _, val := range labelValues {
131-
vh = hashAdd(vh, val)
132-
vh = hashAddByte(vh, separatorByte)
132+
xxh.WriteString(val)
133+
xxh.Write(separatorByteSlice)
133134
}
134-
d.id = vh
135+
d.id = xxh.Sum64()
135136
// Sort labelNames so that order doesn't matter for the hash.
136137
sort.Strings(labelNames)
137138
// Now hash together (in this order) the help string and the sorted
138139
// label names.
139-
lh := hashNew()
140-
lh = hashAdd(lh, help)
141-
lh = hashAddByte(lh, separatorByte)
140+
xxh.Reset()
141+
xxh.WriteString(help)
142+
xxh.Write(separatorByteSlice)
142143
for _, labelName := range labelNames {
143-
lh = hashAdd(lh, labelName)
144-
lh = hashAddByte(lh, separatorByte)
144+
xxh.WriteString(labelName)
145+
xxh.Write(separatorByteSlice)
145146
}
146-
d.dimHash = lh
147+
d.dimHash = xxh.Sum64()
147148

148149
d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
149150
for n, v := range constLabels {

prometheus/metric.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424

2525
const separatorByte byte = 255
2626

27+
var separatorByteSlice = []byte{255} // For convenient use with xxhash.
28+
2729
// A Metric models a single sample value with its meta data being exported to
2830
// Prometheus. Implementations of Metric in this package are Gauge, Counter,
2931
// Histogram, Summary, and Untyped.

prometheus/registry.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"sync"
2626
"unicode/utf8"
2727

28+
"github.com/cespare/xxhash/v2"
2829
"github.com/golang/protobuf/proto"
2930
"github.com/prometheus/common/expfmt"
3031

@@ -875,9 +876,9 @@ func checkMetricConsistency(
875876
}
876877

877878
// Is the metric unique (i.e. no other metric with the same name and the same labels)?
878-
h := hashNew()
879-
h = hashAdd(h, name)
880-
h = hashAddByte(h, separatorByte)
879+
h := xxhash.New()
880+
h.WriteString(name)
881+
h.Write(separatorByteSlice)
881882
// Make sure label pairs are sorted. We depend on it for the consistency
882883
// check.
883884
if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
@@ -888,18 +889,19 @@ func checkMetricConsistency(
888889
dtoMetric.Label = copiedLabels
889890
}
890891
for _, lp := range dtoMetric.Label {
891-
h = hashAdd(h, lp.GetName())
892-
h = hashAddByte(h, separatorByte)
893-
h = hashAdd(h, lp.GetValue())
894-
h = hashAddByte(h, separatorByte)
892+
h.WriteString(lp.GetName())
893+
h.Write(separatorByteSlice)
894+
h.WriteString(lp.GetValue())
895+
h.Write(separatorByteSlice)
895896
}
896-
if _, exists := metricHashes[h]; exists {
897+
hSum := h.Sum64()
898+
if _, exists := metricHashes[hSum]; exists {
897899
return fmt.Errorf(
898900
"collected metric %q { %s} was collected before with the same name and label values",
899901
name, dtoMetric,
900902
)
901903
}
902-
metricHashes[h] = struct{}{}
904+
metricHashes[hSum] = struct{}{}
903905
return nil
904906
}
905907

0 commit comments

Comments
 (0)