Merge pull request #12326 from prometheus/beorn7/storage

storage: optimise sampleRing
This commit is contained in:
Björn Rabenstein 2023-05-05 21:25:23 +02:00 committed by GitHub
commit d61811ef32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 99 additions and 65 deletions

View File

@ -242,15 +242,16 @@ func (s fhSample) Type() chunkenc.ValueType {
type sampleRing struct {
delta int64
// Lookback buffers. We use buf for mixed samples, but one of the three
// Lookback buffers. We use iBuf for mixed samples, but one of the three
// concrete ones for homogenous samples. (Only one of the four bufs is
// allowed to be populated!) This avoids the overhead of the interface
// wrapper for the happy (and by far most common) case of homogenous
// samples.
buf []tsdbutil.Sample
fBuf []fSample
hBuf []hSample
fhBuf []fhSample
iBuf []tsdbutil.Sample
fBuf []fSample
hBuf []hSample
fhBuf []fhSample
bufInUse bufType
i int // Position of most recent element in ring buffer.
f int // Position of first element in ring buffer.
@ -259,6 +260,16 @@ type sampleRing struct {
it sampleRingIterator
}
type bufType int
const (
noBuf bufType = iota // Nothing yet stored in sampleRing.
iBuf
fBuf
hBuf
fhBuf
)
// newSampleRing creates a new sampleRing. If you do not know the prefereed
// value type yet, use a size of 0 (in which case the provided typ doesn't
// matter). On the first add, a buffer of size 16 will be allocated with the
@ -278,7 +289,7 @@ func newSampleRing(delta int64, size int, typ chunkenc.ValueType) *sampleRing {
case chunkenc.ValFloatHistogram:
r.fhBuf = make([]fhSample, size)
default:
r.buf = make([]tsdbutil.Sample, size)
r.iBuf = make([]tsdbutil.Sample, size)
}
return r
}
@ -287,6 +298,7 @@ func (r *sampleRing) reset() {
r.l = 0
r.i = -1
r.f = 0
r.bufInUse = noBuf
}
// Returns the current iterator. Invalidates previously returned iterators.
@ -310,18 +322,18 @@ func (it *sampleRingIterator) Next() chunkenc.ValueType {
if it.i >= it.r.l {
return chunkenc.ValNone
}
switch {
case len(it.r.fBuf) > 0:
switch it.r.bufInUse {
case fBuf:
s := it.r.atF(it.i)
it.t = s.t
it.f = s.f
return chunkenc.ValFloat
case len(it.r.hBuf) > 0:
case hBuf:
s := it.r.atH(it.i)
it.t = s.t
it.h = s.h
return chunkenc.ValHistogram
case len(it.r.fhBuf) > 0:
case fhBuf:
s := it.r.atFH(it.i)
it.t = s.t
it.fh = s.fh
@ -372,8 +384,8 @@ func (it *sampleRingIterator) AtT() int64 {
}
func (r *sampleRing) at(i int) tsdbutil.Sample {
j := (r.f + i) % len(r.buf)
return r.buf[j]
j := (r.f + i) % len(r.iBuf)
return r.iBuf[j]
}
func (r *sampleRing) atF(i int) fSample {
@ -397,91 +409,113 @@ func (r *sampleRing) atFH(i int) fhSample {
// from this package (fSample, hSample, fhSample), call one of the specialized
// methods addF, addH, or addFH for better performance.
func (r *sampleRing) add(s tsdbutil.Sample) {
if len(r.buf) == 0 {
if r.bufInUse == noBuf {
// First sample.
switch s := s.(type) {
case fSample:
r.bufInUse = fBuf
r.fBuf = addF(s, r.fBuf, r)
case hSample:
r.bufInUse = hBuf
r.hBuf = addH(s, r.hBuf, r)
case fhSample:
r.bufInUse = fhBuf
r.fhBuf = addFH(s, r.fhBuf, r)
}
return
}
if r.bufInUse != iBuf {
// Nothing added to the interface buf yet. Let's check if we can
// stay specialized.
switch s := s.(type) {
case fSample:
if len(r.hBuf)+len(r.fhBuf) == 0 {
if r.bufInUse == fBuf {
r.fBuf = addF(s, r.fBuf, r)
return
}
case hSample:
if len(r.fBuf)+len(r.fhBuf) == 0 {
if r.bufInUse == hBuf {
r.hBuf = addH(s, r.hBuf, r)
return
}
case fhSample:
if len(r.fBuf)+len(r.hBuf) == 0 {
if r.bufInUse == fhBuf {
r.fhBuf = addFH(s, r.fhBuf, r)
return
}
}
// The new sample isn't a fit for the already existing
// ones. Copy the latter into the interface buffer where needed.
switch {
case len(r.fBuf) > 0:
switch r.bufInUse {
case fBuf:
for _, s := range r.fBuf {
r.buf = append(r.buf, s)
r.iBuf = append(r.iBuf, s)
}
r.fBuf = nil
case len(r.hBuf) > 0:
case hBuf:
for _, s := range r.hBuf {
r.buf = append(r.buf, s)
r.iBuf = append(r.iBuf, s)
}
r.hBuf = nil
case len(r.fhBuf) > 0:
case fhBuf:
for _, s := range r.fhBuf {
r.buf = append(r.buf, s)
r.iBuf = append(r.iBuf, s)
}
r.fhBuf = nil
}
r.bufInUse = iBuf
}
r.buf = addSample(s, r.buf, r)
r.iBuf = addSample(s, r.iBuf, r)
}
// addF is a version of the add method specialized for fSample.
func (r *sampleRing) addF(s fSample) {
switch {
case len(r.buf) > 0:
// Already have interface samples. Add to the interface buf.
r.buf = addSample(s, r.buf, r)
case len(r.hBuf)+len(r.fhBuf) > 0:
switch r.bufInUse {
case fBuf: // Add to existing fSamples.
r.fBuf = addF(s, r.fBuf, r)
case noBuf: // Add first sample.
r.fBuf = addF(s, r.fBuf, r)
r.bufInUse = fBuf
case iBuf: // Already have interface samples. Add to the interface buf.
r.iBuf = addSample(s, r.iBuf, r)
default:
// Already have specialized samples that are not fSamples.
// Need to call the checked add method for conversion.
r.add(s)
default:
r.fBuf = addF(s, r.fBuf, r)
}
}
// addH is a version of the add method specialized for hSample.
func (r *sampleRing) addH(s hSample) {
switch {
case len(r.buf) > 0:
// Already have interface samples. Add to the interface buf.
r.buf = addSample(s, r.buf, r)
case len(r.fBuf)+len(r.fhBuf) > 0:
// Already have samples that are not hSamples.
switch r.bufInUse {
case hBuf: // Add to existing hSamples.
r.hBuf = addH(s, r.hBuf, r)
case noBuf: // Add first sample.
r.hBuf = addH(s, r.hBuf, r)
r.bufInUse = hBuf
case iBuf: // Already have interface samples. Add to the interface buf.
r.iBuf = addSample(s, r.iBuf, r)
default:
// Already have specialized samples that are not hSamples.
// Need to call the checked add method for conversion.
r.add(s)
default:
r.hBuf = addH(s, r.hBuf, r)
}
}
// addFH is a version of the add method specialized for fhSample.
func (r *sampleRing) addFH(s fhSample) {
switch {
case len(r.buf) > 0:
// Already have interface samples. Add to the interface buf.
r.buf = addSample(s, r.buf, r)
case len(r.fBuf)+len(r.hBuf) > 0:
// Already have samples that are not fhSamples.
switch r.bufInUse {
case fhBuf: // Add to existing fhSamples.
r.fhBuf = addFH(s, r.fhBuf, r)
case noBuf: // Add first sample.
r.fhBuf = addFH(s, r.fhBuf, r)
r.bufInUse = fhBuf
case iBuf: // Already have interface samples. Add to the interface buf.
r.iBuf = addSample(s, r.iBuf, r)
default:
// Already have specialized samples that are not fhSamples.
// Need to call the checked add method for conversion.
r.add(s)
default:
r.fhBuf = addFH(s, r.fhBuf, r)
}
}
@ -701,15 +735,15 @@ func (r *sampleRing) reduceDelta(delta int64) bool {
return true
}
switch {
case len(r.fBuf) > 0:
switch r.bufInUse {
case fBuf:
genericReduceDelta(r.fBuf, r)
case len(r.hBuf) > 0:
case hBuf:
genericReduceDelta(r.hBuf, r)
case len(r.fhBuf) > 0:
case fhBuf:
genericReduceDelta(r.fhBuf, r)
default:
genericReduceDelta(r.buf, r)
genericReduceDelta(r.iBuf, r)
}
return true
}
@ -733,12 +767,12 @@ func (r *sampleRing) nthLast(n int) (tsdbutil.Sample, bool) {
return fSample{}, false
}
i := r.l - n
switch {
case len(r.fBuf) > 0:
switch r.bufInUse {
case fBuf:
return r.atF(i), true
case len(r.hBuf) > 0:
case hBuf:
return r.atH(i), true
case len(r.fhBuf) > 0:
case fhBuf:
return r.atFH(i), true
default:
return r.at(i), true
@ -751,15 +785,15 @@ func (r *sampleRing) samples() []tsdbutil.Sample {
k := r.f + r.l
var j int
switch {
case len(r.buf) > 0:
if k > len(r.buf) {
k = len(r.buf)
switch r.bufInUse {
case iBuf:
if k > len(r.iBuf) {
k = len(r.iBuf)
j = r.l - k + r.f
}
n := copy(res, r.buf[r.f:k])
copy(res[n:], r.buf[:j])
case len(r.fBuf) > 0:
n := copy(res, r.iBuf[r.f:k])
copy(res[n:], r.iBuf[:j])
case fBuf:
if k > len(r.fBuf) {
k = len(r.fBuf)
j = r.l - k + r.f
@ -770,7 +804,7 @@ func (r *sampleRing) samples() []tsdbutil.Sample {
for i, s := range resF {
res[i] = s
}
case len(r.hBuf) > 0:
case hBuf:
if k > len(r.hBuf) {
k = len(r.hBuf)
j = r.l - k + r.f
@ -781,7 +815,7 @@ func (r *sampleRing) samples() []tsdbutil.Sample {
for i, s := range resH {
res[i] = s
}
case len(r.fhBuf) > 0:
case fhBuf:
if k > len(r.fhBuf) {
k = len(r.fhBuf)
j = r.l - k + r.f