Skip to content

Commit fa6d5e2

Browse files
Equalize benchmark cleanup and add fixed_capacity_2048 baseline for symbol pooling comparison (#7412)
* Add a benchmark to compare the fixed symbol with the dynamic Signed-off-by: SungJin1212 <tjdwls1201@gmail.com> * Equalize benchmark cleanup and add fixed_capacity_2048 sub-benchmark The existing benchmark compared dynamic EMA against fixed-128 with unequal cleanup: the dynamic path called full ReuseWriteRequestV2 (Source reset, Timeseries handling, EMA update) while the fixed path only cleared Symbols. This biased timing in favor of the fixed path. Changes: - Equalize cleanup in fixed_capacity to match ReuseWriteRequestV2 (minus the EMA update) - Add fixed_capacity_2048 sub-benchmark to compare against the original #7398 proposal, answering whether EMA complexity is justified over a well-tuned static value Signed-off-by: Friedrich Gonzalez <1517449+friedrichg@users.noreply.github.com> --------- Signed-off-by: SungJin1212 <tjdwls1201@gmail.com> Signed-off-by: Friedrich Gonzalez <1517449+friedrichg@users.noreply.github.com> Co-authored-by: SungJin1212 <tjdwls1201@gmail.com>
1 parent 294934e commit fa6d5e2

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

pkg/cortexpb/timeseriesv2_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package cortexpb
22

33
import (
4+
"fmt"
5+
"runtime"
6+
"sync"
47
"testing"
58

69
"github.com/gogo/protobuf/proto"
@@ -183,3 +186,144 @@ func BenchmarkMarshallWriteRequestV2(b *testing.B) {
183186
})
184187
}
185188
}
189+
190+
func BenchmarkWriteRequestV2Pool_CompareFixedSymbolCapaWithDynamic(b *testing.B) {
191+
const (
192+
numVariants = 10
193+
gcInterval = 100
194+
)
195+
196+
scenarios := []struct {
197+
name string
198+
symbolMin, symbolMax int
199+
}{
200+
{"symbol_range_200_2000", 200, 2000},
201+
{"symbol_range_1000_6000", 1000, 6000},
202+
{"symbol_range_1000_20000 (exceed maxSymbolsCapacity)", 1000, 20000},
203+
}
204+
205+
for _, sc := range scenarios {
206+
variants := make([][]string, numVariants)
207+
for i := range numVariants {
208+
size := sc.symbolMin + (sc.symbolMax-sc.symbolMin)*i/(numVariants-1)
209+
syms := make([]string, size)
210+
for j := range syms {
211+
syms[j] = fmt.Sprintf("label_%04d", j)
212+
}
213+
variants[i] = syms
214+
}
215+
216+
b.Run(sc.name, func(b *testing.B) {
217+
b.Run("dynamic_capacity", func(b *testing.B) {
218+
saved := dynamicSymbolsCapacity.Load()
219+
dynamicSymbolsCapacity.Store(int64(initialSymbolsCapacity))
220+
defer dynamicSymbolsCapacity.Store(saved)
221+
222+
// Converge EMA to ensure dynamic capacity is adjusted to the range of variants.
223+
for i := range 100 * numVariants {
224+
req := PreallocWriteRequestV2FromPool()
225+
req.Symbols = append(req.Symbols, variants[i%numVariants]...)
226+
ReuseWriteRequestV2(req)
227+
}
228+
229+
b.ReportAllocs()
230+
var idx int
231+
for b.Loop() {
232+
if idx > 0 && idx%gcInterval == 0 {
233+
runtime.GC() // Periodically run GC to simulate real-world conditions where the pool may be emptied.
234+
}
235+
req := PreallocWriteRequestV2FromPool()
236+
req.Symbols = append(req.Symbols, variants[idx%numVariants]...)
237+
idx++
238+
ReuseWriteRequestV2(req)
239+
}
240+
})
241+
242+
b.Run("fixed_capacity", func(b *testing.B) {
243+
fixedPool := sync.Pool{
244+
New: func() any {
245+
return &PreallocWriteRequestV2{
246+
WriteRequestV2: WriteRequestV2{
247+
Symbols: make([]string, 0, initialSymbolsCapacity),
248+
},
249+
}
250+
},
251+
}
252+
253+
b.ReportAllocs()
254+
var idx int
255+
for b.Loop() {
256+
if idx > 0 && idx%gcInterval == 0 {
257+
runtime.GC()
258+
}
259+
req := fixedPool.Get().(*PreallocWriteRequestV2)
260+
req.Symbols = append(req.Symbols, variants[idx%numVariants]...)
261+
idx++
262+
263+
// Same cleanup as ReuseWriteRequestV2, minus the EMA update.
264+
req.Source = 0
265+
symbolsCap := int64(cap(req.Symbols))
266+
if symbolsCap > maxSymbolsCapacity {
267+
if req.Timeseries != nil {
268+
ReuseSliceV2(req.Timeseries)
269+
req.Timeseries = nil
270+
}
271+
continue
272+
}
273+
for i := range req.Symbols {
274+
req.Symbols[i] = ""
275+
}
276+
req.Symbols = req.Symbols[:0]
277+
if req.Timeseries != nil {
278+
ReuseSliceV2(req.Timeseries)
279+
req.Timeseries = nil
280+
}
281+
fixedPool.Put(req)
282+
}
283+
})
284+
285+
b.Run("fixed_capacity_2048", func(b *testing.B) {
286+
fixedPool2048 := sync.Pool{
287+
New: func() any {
288+
return &PreallocWriteRequestV2{
289+
WriteRequestV2: WriteRequestV2{
290+
Symbols: make([]string, 0, 2048),
291+
},
292+
}
293+
},
294+
}
295+
296+
b.ReportAllocs()
297+
var idx int
298+
for b.Loop() {
299+
if idx > 0 && idx%gcInterval == 0 {
300+
runtime.GC()
301+
}
302+
req := fixedPool2048.Get().(*PreallocWriteRequestV2)
303+
req.Symbols = append(req.Symbols, variants[idx%numVariants]...)
304+
idx++
305+
306+
// Same cleanup as ReuseWriteRequestV2, minus the EMA update.
307+
req.Source = 0
308+
symbolsCap := int64(cap(req.Symbols))
309+
if symbolsCap > maxSymbolsCapacity {
310+
if req.Timeseries != nil {
311+
ReuseSliceV2(req.Timeseries)
312+
req.Timeseries = nil
313+
}
314+
continue
315+
}
316+
for i := range req.Symbols {
317+
req.Symbols[i] = ""
318+
}
319+
req.Symbols = req.Symbols[:0]
320+
if req.Timeseries != nil {
321+
ReuseSliceV2(req.Timeseries)
322+
req.Timeseries = nil
323+
}
324+
fixedPool2048.Put(req)
325+
}
326+
})
327+
})
328+
}
329+
}

0 commit comments

Comments
 (0)