From b0c915a115ddc79baa5499fad067bbf119bcfacf Mon Sep 17 00:00:00 2001 From: Banana Duck Date: Sun, 4 Jan 2026 14:19:25 +0300 Subject: [PATCH 1/6] benchmarks tidied up --- cache/cache_test.go | 8 +- frac/active_ids_test.go | 19 +-- frac/processor/aggregator_test.go | 29 ++-- indexer/processor_test.go | 6 +- node/bench_test.go | 251 +++++++++++++++++++++--------- parser/seqql_filter_test.go | 4 +- pattern/pattern_test.go | 1 - proxy/bulk/ingestor_test.go | 6 +- proxyapi/http_bulk_test.go | 7 +- seq/qpr_test.go | 5 +- tests/setup/doc_test.go | 77 ++++++--- util/bitmask_test.go | 21 +-- 12 files changed, 283 insertions(+), 151 deletions(-) diff --git a/cache/cache_test.go b/cache/cache_test.go index 8ef40be9..59eb223e 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -129,19 +129,17 @@ func TestStress(t *testing.T) { } func BenchmarkBucketClean(b *testing.B) { - b.StopTimer() - cleaner := NewCleaner(0, nil) c := NewCache[int](cleaner, nil) - for r := 0; r < b.N; r++ { - for i := 0; i < 1000; i++ { + for b.Loop() { + b.StopTimer() + for i := range 1000 { c.Get(uint32(i), func() (int, int) { return i, 4 }) } cleaner.markStale(cleaner.getSize()) b.StartTimer() size := c.Cleanup() - b.StopTimer() if size == 0 { b.FailNow() } diff --git a/frac/active_ids_test.go b/frac/active_ids_test.go index ae2f6111..d1edffb1 100644 --- a/frac/active_ids_test.go +++ b/frac/active_ids_test.go @@ -5,9 +5,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.uber.org/zap" - - "github.com/ozontech/seq-db/logger" ) func TestSeqListAppend(t *testing.T) { @@ -36,13 +33,13 @@ func BenchmarkMutexListAppend(b *testing.B) { gr := 2 mu := sync.Mutex{} b.SetBytes(int64(gr * 8000000)) - for n := 0; n < b.N; n++ { + for b.Loop() { list := make([]uint64, 0) wg := sync.WaitGroup{} wg.Add(gr) - for i := 0; i < gr; i++ { + for range gr { go func() { - for x := 0; x < 800000; x++ { + for x := range 800000 { mu.Lock() list = append(list, uint64(x)) mu.Unlock() @@ -51,7 +48,7 @@ func BenchmarkMutexListAppend(b *testing.B) { }() } wg.Wait() - logger.Info("list", zap.Int("total", len(list))) + b.Logf("list_total=%d", len(list)) } } @@ -59,19 +56,19 @@ func BenchmarkMutexListAppend(b *testing.B) { func BenchmarkSeqListAppend(b *testing.B) { gr := 2 b.SetBytes(int64(gr * 8000000)) - for n := 0; n < b.N; n++ { + for b.Loop() { list := NewIDs() wg := sync.WaitGroup{} wg.Add(gr) - for i := 0; i < gr; i++ { + for range gr { go func() { - for x := 0; x < 800000; x++ { + for x := range 800000 { list.Append(uint64(x)) } wg.Done() }() } wg.Wait() - logger.Info("list", zap.Uint32("total", list.Len())) + b.Logf("list_total=%d", list.Len()) } } diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index 2bbd2186..3482ad6a 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -1,6 +1,7 @@ package processor import ( + "fmt" "math" "math/rand" "reflect" @@ -89,16 +90,24 @@ func Generate(n int) ([]uint32, uint32) { } func BenchmarkAggDeep(b *testing.B) { - v, _ := Generate(b.N) - src := node.NewSourcedNodeWrapper(node.NewStatic(v, false), 0) - iter := NewSourcedNodeIterator(src, nil, make([]uint32, 1), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) - n := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) - vals, _ := Generate(b.N) - b.ResetTimer() - for _, v := range vals { - if err := n.Next(v); err != nil { - b.Fatal(err) - } + dataLen := []int{1000, 10_000, 1_000_000} + + for _, dl := range dataLen { + b.Run(fmt.Sprintf("data_len=%d", dl), func(b *testing.B) { + v, _ := Generate(dl) + src := node.NewSourcedNodeWrapper(node.NewStatic(v, false), 0) + iter := NewSourcedNodeIterator(src, nil, make([]uint32, 1), 0, false) + n := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) + vals, _ := Generate(dl) + + for b.Loop() { + for _, v := range vals { + if err := n.Next(v); err != nil { + b.Fatal(err) + } + } + } + }) } } diff --git a/indexer/processor_test.go b/indexer/processor_test.go index 0b25c5f3..7fbe2566 100644 --- a/indexer/processor_test.go +++ b/indexer/processor_test.go @@ -89,7 +89,7 @@ func BenchmarkParseESTime(b *testing.B) { const toParseRFC3339 = "2024-04-19T18:04:25.999Z" b.Run("es_stdlib", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { _, err := time.Parse(consts.ESTimeFormat, toParse) if err != nil { b.Fatal(err) @@ -98,7 +98,7 @@ func BenchmarkParseESTime(b *testing.B) { }) b.Run("handwritten", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { _, ok := parseESTime(toParse) if !ok { b.Fatal() @@ -107,7 +107,7 @@ func BenchmarkParseESTime(b *testing.B) { }) b.Run("rfc3339", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { _, err := time.Parse(time.RFC3339, toParseRFC3339) if err != nil { b.Fatal(err) diff --git a/node/bench_test.go b/node/bench_test.go index d16e50e6..a1ea47f5 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -1,6 +1,7 @@ package node import ( + "fmt" "math/rand" "testing" @@ -25,107 +26,215 @@ func Generate(n int) ([]uint32, uint32) { // bench for base point // you can't go faster func BenchmarkCopy(b *testing.B) { - res := make([]uint32, b.N) - n := newNodeStaticSize(b.N) - b.ResetTimer() - copy(res, n.data) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + res := make([]uint32, s) + n := newNodeStaticSize(s) + + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + for b.Loop() { + copy(res, n.data) + } + }) + + if len(res) == 0 { + b.FailNow() + } + } } // base point func BenchmarkIterate(b *testing.B) { - res := make([]uint32, b.N) - n := newNodeStaticSize(b.N) - b.ResetTimer() - for i, v := range n.data { - res[i] = v + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + res := make([]uint32, s) + n := newNodeStaticSize(s) + + for b.Loop() { + for i, v := range n.data { + res[i] = v + } + } + + if len(res) == 0 { + b.FailNow() + } + }) } } func BenchmarkStatic(b *testing.B) { - res := make([]uint32, 0, b.N) - n := newNodeStaticSize(b.N) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + res := make([]uint32, 0, s) + n := newNodeStaticSize(s) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s) + }) + } } func BenchmarkNot(b *testing.B) { - v, last := Generate(b.N) - res := make([]uint32, 0, last+1) - n := NewNot(NewStatic(v, false), 1, last, false) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), int(last)+1) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + v, last := Generate(s) + res := make([]uint32, 0, last+1) + n := NewNot(NewStatic(v, false), 1, last, false) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), int(last)+1) + }) + + } } func BenchmarkNotEmpty(b *testing.B) { - res := make([]uint32, 0, b.N*2) - n := NewNot(NewStatic(nil, false), 1, uint32(b.N), false) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N*2) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + res := make([]uint32, 0, s*2) + n := NewNot(NewStatic(nil, false), 1, uint32(s), false) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s*2) + }) + } + } func BenchmarkOr(b *testing.B) { - res := make([]uint32, 0, b.N*2) - n := NewOr(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N*2) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + res := make([]uint32, 0, s*2) + n := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s*2) + }) + } } func BenchmarkAnd(b *testing.B) { - res := make([]uint32, 0, b.N) - n := NewAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + res := make([]uint32, 0, s) + n := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s) + }) + } } func BenchmarkNAnd(b *testing.B) { - res := make([]uint32, 0, b.N) - n := NewNAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + res := make([]uint32, 0, s) + n := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s) + }) + } } func BenchmarkAndTree(b *testing.B) { - n1 := NewAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n2 := NewAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n3 := NewAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n4 := NewAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n12 := NewAnd(n1, n2, false) - n34 := NewAnd(n3, n4, false) - n := NewAnd(n12, n34, false) - res := make([]uint32, 0, b.N) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n2 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n3 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n4 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n12 := NewAnd(n1, n2, false) + n34 := NewAnd(n3, n4, false) + n := NewAnd(n12, n34, false) + res := make([]uint32, 0, s) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s) + }) + } } func BenchmarkOrTree(b *testing.B) { - n1 := NewOr(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n2 := NewOr(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n3 := NewOr(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n4 := NewOr(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n12 := NewOr(n1, n2, false) - n34 := NewOr(n3, n4, false) - n := NewOr(n12, n34, false) - res := make([]uint32, 0, b.N*8) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N*8) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + n1 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) + n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) + n3 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) + n4 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) + n12 := NewOr(n1, n2, false) + n34 := NewOr(n3, n4, false) + n := NewOr(n12, n34, false) + res := make([]uint32, 0, s*8) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s*8) + + }) + } } func BenchmarkComplex(b *testing.B) { - res := make([]uint32, 0, b.N*2) - n1 := NewAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n2 := NewOr(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n3 := NewNAnd(newNodeStaticSize(b.N), newNodeStaticSize(b.N), false) - n12 := NewOr(n1, n2, false) - n := NewAnd(n12, n3, false) - b.ResetTimer() - res = readAllInto(n, res) - assert.Equal(b, cap(res), b.N*2) + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + res := make([]uint32, 0, s*2) + n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) + n3 := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n12 := NewOr(n1, n2, false) + n := NewAnd(n12, n3, false) + + for b.Loop() { + res = readAllInto(n, res) + } + + assert.Equal(b, cap(res), s*2) + }) + } } diff --git a/parser/seqql_filter_test.go b/parser/seqql_filter_test.go index edffbc22..cefeb986 100644 --- a/parser/seqql_filter_test.go +++ b/parser/seqql_filter_test.go @@ -483,7 +483,7 @@ func BenchmarkSeqQLParsing(b *testing.B) { var query SeqQLQuery var err error str := `service: "some service" AND level:1` - for i := 0; i < b.N; i++ { + for b.Loop() { query, err = ParseSeqQL(str, seq.TestMapping) if err != nil { b.Fatal(err.Error()) @@ -496,7 +496,7 @@ func BenchmarkSeqQLParsingLong(b *testing.B) { var query SeqQLQuery var err error str := `((NOT ((((m:19 OR m:20) OR m:18) AND m:16) OR ((NOT (m:25 OR m:26)) AND m:12))) OR (((NOT m:29) AND m:22) OR (((m:31 OR m:32) AND m:14) OR (m:27 AND m:28))))` - for i := 0; i < b.N; i++ { + for b.Loop() { query, err = ParseSeqQL(str, seq.TestMapping) if err != nil { b.Fatal(err.Error()) diff --git a/pattern/pattern_test.go b/pattern/pattern_test.go index 2c9a5702..9511cdd5 100644 --- a/pattern/pattern_test.go +++ b/pattern/pattern_test.go @@ -574,7 +574,6 @@ func BenchmarkFindSequence_Random(b *testing.B) { haystack, needles := generateTestData( size.haystackSize, size.needleSize, size.needleCount, 256, ) - b.ResetTimer() for b.Loop() { findSequence(haystack, needles) b.SetBytes(int64(len(haystack))) diff --git a/proxy/bulk/ingestor_test.go b/proxy/bulk/ingestor_test.go index e38a9180..5557c3dc 100644 --- a/proxy/bulk/ingestor_test.go +++ b/proxy/bulk/ingestor_test.go @@ -495,8 +495,8 @@ func BenchmarkProcessDocuments(b *testing.B) { "level":"error", "timestamp":%q, "message":"невозможно сохранить данные в шарде", - "error":"circuit breaker execute: can't receive bulk acceptance: - host=***REMOVED***, err=rpc error: code = Unavailable desc = connection error: + "error":"circuit breaker execute: can't receive bulk acceptance: + host=***REMOVED***, err=rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp 10.233.140.20:9002: connect: connection refused\"", "shard":0 }`, time.Now().Format(consts.ESTimeFormat))) @@ -506,7 +506,7 @@ func BenchmarkProcessDocuments(b *testing.B) { b.SetBytes(int64(len(doc) * docsToProcess)) - for i := 0; i < b.N; i++ { + for b.Loop() { n := 0 total, err := ingestor.ProcessDocuments(ctx, now, func() ([]byte, error) { n++ diff --git a/proxyapi/http_bulk_test.go b/proxyapi/http_bulk_test.go index 820e9b25..b1e0f5fc 100644 --- a/proxyapi/http_bulk_test.go +++ b/proxyapi/http_bulk_test.go @@ -46,7 +46,7 @@ func BenchmarkESBulk(b *testing.B) { const totalSize = len(payload) * docsToLoad buf := make([]byte, 0, totalSize) - for i := 0; i < docsToLoad; i++ { + for range docsToLoad { buf = append(buf, []byte(payload)...) buf = append(buf, '\n') } @@ -64,8 +64,7 @@ func BenchmarkESBulk(b *testing.B) { b.SetBytes(int64(totalSize)) b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { _, _ = reqBodyBuf.Seek(0, io.SeekStart) response.Body.Reset() proc.Count = 0 @@ -73,7 +72,7 @@ func BenchmarkESBulk(b *testing.B) { handler.ServeHTTP(response, &request) if proc.Count != docsToLoad { - b.Fatalf("expected %d docs, got %d", docsToLoad*b.N, proc.Count) + b.Fatalf("expected %d docs, got %d", docsToLoad, proc.Count) } } } diff --git a/seq/qpr_test.go b/seq/qpr_test.go index e44a133d..14b6f253 100644 --- a/seq/qpr_test.go +++ b/seq/qpr_test.go @@ -519,18 +519,17 @@ func BenchmarkMergeQPRs_ReusingQPR(b *testing.B) { qprSize := uint64(100) qprs := make([]*QPR, totalQPRs) - for i := uint64(0); i < totalQPRs; i++ { + for i := range totalQPRs { qpr := getRandomQPR(qprSize) qprs[i] = &qpr } - b.ResetTimer() aggQpr := QPR{ Histogram: make(map[MID]uint64), Aggs: make([]AggregatableSamples, int(totalQPRs)), } - for range b.N { + for b.Loop() { MergeQPRs(&aggQpr, qprs, 1000, 5, DocsOrderDesc) aggQpr.IDs = aggQpr.IDs[:0] diff --git a/tests/setup/doc_test.go b/tests/setup/doc_test.go index bf463de1..483fd5e4 100644 --- a/tests/setup/doc_test.go +++ b/tests/setup/doc_test.go @@ -2,7 +2,7 @@ package setup import ( "encoding/json" - "sync" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -42,22 +42,10 @@ func TestRandomDocJSON(t *testing.T) { } } -func RunParallel(n int, f func()) { - wait := sync.WaitGroup{} - for j := 0; j < n; j++ { - wait.Add(1) - go func() { - defer wait.Done() - f() - }() - } - wait.Wait() -} - func BenchmarkRandomJSON(b *testing.B) { - RunParallel(4, func() { + b.RunParallel(func(pb *testing.PB) { sum := 0 - for i := 0; i < b.N; i++ { + for pb.Next() { res := RandomJSON(50) sum += len(res) _ = res @@ -67,9 +55,9 @@ func BenchmarkRandomJSON(b *testing.B) { } func BenchmarkRandomDoc(b *testing.B) { - RunParallel(4, func() { + b.RunParallel(func(pb *testing.PB) { sum := 0 - for i := 0; i < b.N; i++ { + for pb.Next() { res := RandomDoc(1) sum += len(res.Message) _ = res @@ -79,18 +67,57 @@ func BenchmarkRandomDoc(b *testing.B) { } func BenchmarkGenerateDocs(b *testing.B) { - res := GenerateDocs(b.N, func(_ int, doc *ExampleDoc) { - *doc = *RandomDoc(1) - }) - _ = res + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + var res []ExampleDoc + + for b.Loop() { + res = GenerateDocs(s, func(_ int, doc *ExampleDoc) { + *doc = *RandomDoc(1) + }) + } + + if len(res) == 0 { + b.FailNow() + } + }) + } } func BenchmarkGenerateDocsJSON(b *testing.B) { - res := GenerateDocsJSON(b.N, false) - _ = res + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + var res [][]byte + + for b.Loop() { + res = GenerateDocsJSON(s, false) + } + + if len(res) == 0 { + b.FailNow() + } + }) + } } func BenchmarkGenerateDocsJSONFields(b *testing.B) { - res := GenerateDocsJSON(b.N, true) - _ = res + sizes := []int{1000, 10_000, 1_000_000} + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + var res [][]byte + + for b.Loop() { + res = GenerateDocsJSON(s, true) + } + + if len(res) == 0 { + b.FailNow() + } + }) + } } diff --git a/util/bitmask_test.go b/util/bitmask_test.go index 81b21e17..68026c12 100644 --- a/util/bitmask_test.go +++ b/util/bitmask_test.go @@ -79,25 +79,20 @@ func TestSimpleBitmask(t *testing.T) { assert.True(t, bm.HasBitsIn(maxSize/3*2+8, maxSize-1)) } -func BenchmarkBitmask(b *testing.B) { +func BenchmarkBitmask_HasBitsIn(b *testing.B) { const size = 10000 - b.StopTimer() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - bm := NewBitmask(size) - for i := 0; i < size; i++ { - v := rand.Int31n(2) == 1 - bm.Set(i, v) - } + bm := NewBitmask(size) + for i := range size { + v := rand.Int31n(2) == 1 + bm.Set(i, v) + } - b.StartTimer() - for i := 0; i < size-1; i++ { + for b.Loop() { + for i := range size - 1 { for j := i + 1; j < size; j++ { bm.HasBitsIn(i, j) } } - b.StopTimer() } } From 3cad2549e1856f56a61e3ae1e89b4c7567a5855d Mon Sep 17 00:00:00 2001 From: Banana Duck Date: Sun, 4 Jan 2026 15:11:58 +0300 Subject: [PATCH 2/6] new benchmarks tidied up --- frac/active_indexer_test.go | 2 +- frac/processor/aggregator_test.go | 56 ++++++++++++++++++------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/frac/active_indexer_test.go b/frac/active_indexer_test.go index fc2585c6..17fb5230 100644 --- a/frac/active_indexer_test.go +++ b/frac/active_indexer_test.go @@ -94,7 +94,7 @@ func BenchmarkIndexer(b *testing.B) { processor := getTestProcessor() - for i := 0; i < b.N; i++ { + for b.Loop() { b.StopTimer() bulks := make([][]byte, 0, len(readers)) for _, readNext := range readers { diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index 3482ad6a..9976f2b9 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -90,15 +90,15 @@ func Generate(n int) ([]uint32, uint32) { } func BenchmarkAggDeep(b *testing.B) { - dataLen := []int{1000, 10_000, 1_000_000} + sizes := []int{1_000, 10_000, 1_000_000} - for _, dl := range dataLen { - b.Run(fmt.Sprintf("data_len=%d", dl), func(b *testing.B) { - v, _ := Generate(dl) + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + v, _ := Generate(s) src := node.NewSourcedNodeWrapper(node.NewStatic(v, false), 0) - iter := NewSourcedNodeIterator(src, nil, make([]uint32, 1), 0, false) + iter := NewSourcedNodeIterator(src, nil, make([]uint32, 1), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) n := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) - vals, _ := Generate(dl) + vals, _ := Generate(s) for b.Loop() { for _, v := range vals { @@ -112,27 +112,35 @@ func BenchmarkAggDeep(b *testing.B) { } func BenchmarkAggWide(b *testing.B) { - v, _ := Generate(b.N) + sizes := []int{1_000, 10_000, 1_000_000} - factor := int(math.Sqrt(float64(b.N))) - wide := make([][]uint32, b.N/factor) - for i := range wide { - for range factor { - wide[i] = append(wide[i], v[rand.Intn(b.N)]) - } - slices.Sort(wide[i]) - } + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + v, _ := Generate(s) + + factor := int(math.Sqrt(float64(s))) + wide := make([][]uint32, s/factor) + for i := range wide { + for range factor { + wide[i] = append(wide[i], v[rand.Intn(s)]) + } + slices.Sort(wide[i]) + } - source := node.BuildORTreeAgg(node.MakeStaticNodes(wide), false) + source := node.BuildORTreeAgg(node.MakeStaticNodes(wide), false) - iter := NewSourcedNodeIterator(source, nil, make([]uint32, len(wide)), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) - n := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) - vals, _ := Generate(b.N) - b.ResetTimer() - for _, v := range vals { - if err := n.Next(v); err != nil { - b.Fatal(err) - } + iter := NewSourcedNodeIterator(source, nil, make([]uint32, len(wide)), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) + n := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) + vals, _ := Generate(s) + + for b.Loop() { + for _, v := range vals { + if err := n.Next(v); err != nil { + b.Fatal(err) + } + } + } + }) } } From ee059b282fda16346e51410685f9990d6c314ef3 Mon Sep 17 00:00:00 2001 From: Daniil Porokhnin Date: Thu, 20 Nov 2025 14:40:29 +0300 Subject: [PATCH 3/6] ci: increase `-count` to 5 on `pull_request` --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index f4271f1d..05b33fce 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -45,7 +45,7 @@ jobs: - name: Run Golang benchmarks shell: bash run: | - COUNT=$([[ "${{ github.event_name }}" == "pull_request" ]] && echo 1 || echo 5) + COUNT=$([[ "${{ github.event_name }}" == "pull_request" ]] && echo 5 || echo 5) LOG_LEVEL=FATAL GOMAXPROCS=4 go test -run='$^' -count=$COUNT -bench=. -benchmem ./... | tee ${{ runner.temp }}/gotool.txt # Add GitHub job summary for pull requests. From 8da327c5e2661a75eb80bc935e6aed5902b7f482 Mon Sep 17 00:00:00 2001 From: Banana Duck Date: Sat, 24 Jan 2026 19:13:17 +0300 Subject: [PATCH 4/6] review fixes --- .github/workflows/benchmarks.yml | 8 +- cache/cache_test.go | 4 + frac/active_ids_test.go | 3 - fracmanager/storage-state.json | 1 + node/bench_test.go | 61 --------- tests/setup/doc.go | 218 +------------------------------ tests/setup/doc_test.go | 123 ----------------- util/bitmask_test.go | 2 +- 8 files changed, 11 insertions(+), 409 deletions(-) create mode 100644 fracmanager/storage-state.json delete mode 100644 tests/setup/doc_test.go diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 05b33fce..35d8ef7b 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -4,9 +4,9 @@ name: Benchmarks on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] permissions: issues: write @@ -39,13 +39,13 @@ jobs: - name: Setup Golang uses: actions/setup-go@v6 with: - go-version: '1.24' + go-version: "1.24" cache: true - name: Run Golang benchmarks shell: bash run: | - COUNT=$([[ "${{ github.event_name }}" == "pull_request" ]] && echo 5 || echo 5) + COUNT=$([[ "${{ github.event_name }}" == "pull_request" ]] && echo 10 || echo 15) LOG_LEVEL=FATAL GOMAXPROCS=4 go test -run='$^' -count=$COUNT -bench=. -benchmem ./... | tee ${{ runner.temp }}/gotool.txt # Add GitHub job summary for pull requests. diff --git a/cache/cache_test.go b/cache/cache_test.go index 59eb223e..9c9d6730 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -134,11 +134,15 @@ func BenchmarkBucketClean(b *testing.B) { for b.Loop() { b.StopTimer() + for i := range 1000 { c.Get(uint32(i), func() (int, int) { return i, 4 }) } + cleaner.markStale(cleaner.getSize()) + b.StartTimer() + size := c.Cleanup() if size == 0 { b.FailNow() diff --git a/frac/active_ids_test.go b/frac/active_ids_test.go index d1edffb1..b089cc98 100644 --- a/frac/active_ids_test.go +++ b/frac/active_ids_test.go @@ -48,9 +48,7 @@ func BenchmarkMutexListAppend(b *testing.B) { }() } wg.Wait() - b.Logf("list_total=%d", len(list)) } - } func BenchmarkSeqListAppend(b *testing.B) { @@ -69,6 +67,5 @@ func BenchmarkSeqListAppend(b *testing.B) { }() } wg.Wait() - b.Logf("list_total=%d", list.Len()) } } diff --git a/fracmanager/storage-state.json b/fracmanager/storage-state.json new file mode 100644 index 00000000..548d6cf0 --- /dev/null +++ b/fracmanager/storage-state.json @@ -0,0 +1 @@ +{"capacity_exceeded":false} \ No newline at end of file diff --git a/node/bench_test.go b/node/bench_test.go index a1ea47f5..6f2c5b7c 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -23,66 +23,6 @@ func Generate(n int) ([]uint32, uint32) { return v, last } -// bench for base point -// you can't go faster -func BenchmarkCopy(b *testing.B) { - sizes := []int{1000, 10_000, 1_000_000} - - for _, s := range sizes { - res := make([]uint32, s) - n := newNodeStaticSize(s) - - b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - for b.Loop() { - copy(res, n.data) - } - }) - - if len(res) == 0 { - b.FailNow() - } - } -} - -// base point -func BenchmarkIterate(b *testing.B) { - sizes := []int{1000, 10_000, 1_000_000} - - for _, s := range sizes { - b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - res := make([]uint32, s) - n := newNodeStaticSize(s) - - for b.Loop() { - for i, v := range n.data { - res[i] = v - } - } - - if len(res) == 0 { - b.FailNow() - } - }) - } -} - -func BenchmarkStatic(b *testing.B) { - sizes := []int{1000, 10_000, 1_000_000} - - for _, s := range sizes { - b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - res := make([]uint32, 0, s) - n := newNodeStaticSize(s) - - for b.Loop() { - res = readAllInto(n, res) - } - - assert.Equal(b, cap(res), s) - }) - } -} - func BenchmarkNot(b *testing.B) { sizes := []int{1000, 10_000, 1_000_000} @@ -98,7 +38,6 @@ func BenchmarkNot(b *testing.B) { assert.Equal(b, cap(res), int(last)+1) }) - } } diff --git a/tests/setup/doc.go b/tests/setup/doc.go index 6721f291..45ce8a3f 100644 --- a/tests/setup/doc.go +++ b/tests/setup/doc.go @@ -2,14 +2,8 @@ package setup import ( "encoding/json" - "runtime" - "strings" - "sync" "time" - - "lukechampine.com/frand" // much faster with multiple goroutines - - "github.com/ozontech/seq-db/tests/common" + // much faster with multiple goroutines ) // InlineJSON is a string representing valid json @@ -39,159 +33,6 @@ type ExampleDoc struct { Timestamp time.Time `json:"timestamp,omitempty"` } -func randomStringFromPool(pool []string) string { - ind := frand.Intn(len(pool)) - if pool[ind] == "" { - return RandomString(3, 10) - } - return pool[ind] -} - -// RandomString calls common.RandomString -// it is here, so you could call `setup.RandomSomething`, without exceptions -func RandomString(minSize, maxSize int) string { - return common.RandomString(minSize, maxSize) -} - -// ChanceValue returns given string with probability prob%, or empty string instead -func ChanceValue(prob int, value string) string { - if frand.Intn(100) <= prob { - return value - } - return "" -} - -// RandomChanceString returns random string with probability prob%, or empty string instead -func RandomChanceString(prob, minSize, maxSize int) string { - return ChanceValue(prob, RandomString(minSize, maxSize)) -} - -var services = []string{ - "metatarrificator", "trinity-storage", "product-facade", "rtb-api", - "marketing-action-api", "s3front", "k8s-audit", "scout-collector", - "", // will be random -} - -func RandomService() string { - return randomStringFromPool(services) -} - -// lorem ipsum -var words = []string{ - "Suspendisse", "a", "massa", "eget", "turpis", "efficitur", "suscipit", - "Proin", "quis", "nibh", "urna", "Suspendisse", "sed", "leo", "porta", - "lacinia", "dui", "at", "viverra", "enim", "Cras", "sodales", "dolor", - "eu", "malesuada", "suscipit", "Maecenas", "eget", "eros", "tempor", "felis", - "varius", "dapibus", "a", "non", "ipsum", "Nulla", "facilisi", "Vivamus", "id", - "", "", "", // will be random -} - -func RandomWord() string { - return randomStringFromPool(words) -} - -func RandomSymbol() byte { - symbols := "!@#$%^&*(){}[]:?/\\~;" - return symbols[frand.Intn(len(symbols))] -} - -func randomJSON(size int, builder *strings.Builder) { - keys := map[string]struct{}{} - // much faster to do with builders - builder.WriteByte('{') - for i := 0; i < size; i++ { - // remove duplicate keys, because that's invalid json - key := RandomString(1, 10) - if _, ok := keys[key]; ok { - i-- - continue - } - keys[key] = struct{}{} - - if i > 0 { - builder.WriteByte(',') - } - builder.WriteByte('"') - builder.WriteString(key) - // with some chance write new subsized json - // else write just a string - if frand.Intn(10) == 0 { - subSize := min(frand.Intn(max(size/10, 1))+1, size-i-1) - builder.WriteString(`":`) - randomJSON(subSize, builder) - i += subSize - } else { - builder.WriteString(`":"`) - builder.WriteString(RandomString(3, 20)) - builder.WriteByte('"') - } - } - builder.WriteByte('}') -} - -// RandomText generates string with random words and symbols inbetween -func RandomText(size int) string { - builder := strings.Builder{} - for i := 0; i < size; i++ { - builder.WriteString(RandomWord()) - if frand.Intn(10) == 0 { - builder.WriteByte(RandomSymbol()) - } - builder.WriteByte(' ') - } - return builder.String() -} - -// RandomJSON generates json with random string keys -// and another random json or random string as values -func RandomJSON(size int) string { - builder := strings.Builder{} - randomJSON(max(1, size), &builder) - return builder.String() -} - -func RandomDoc(sizeScale int) *ExampleDoc { - return &ExampleDoc{ - Service: RandomService(), - Message: RandomText(10 * sizeScale), - TraceID: RandomChanceString(90, 8, 10), - Source: RandomChanceString(10, 8, 10), - Zone: randomStringFromPool([]string{"z23", "z501", "z502", "z26"}), - Level: frand.Intn(6), - Timestamp: time.Now(), - RequiestObject: InlineJSON(RandomJSON(1 * sizeScale)), - } -} - -func MergeJSONs(a, b []byte) []byte { - c := append([]byte(nil), a[:len(a)-1]...) - c = append(c, ',') - c = append(c, b[1:]...) - return c -} - -// RandomDocJSON creates random doc and adds to it some additional fields -func RandomDocJSON(sizeScale, addFields int) []byte { - doc := RandomDoc(sizeScale) - docbuf, err := json.Marshal(doc) - if err != nil { - panic(err) - } - if addFields == 0 { - return docbuf - } - mp := map[string]string{} - for i := 0; i < addFields; i++ { - mp[RandomString(3, 10)] = RandomString(10, 20) - } - mapbuf, err := json.Marshal(mp) - if err != nil { - panic(err) - } - b := MergeJSONs(docbuf, mapbuf) - return b -} - func DocsToStrings(docs []ExampleDoc) []string { docStr := make([]string, 0, len(docs)) for i := 0; i < len(docs); i++ { @@ -203,60 +44,3 @@ func DocsToStrings(docs []ExampleDoc) []string { } return docStr } - -func splitRange(size int, callback func(from int, to int)) { - routines := max(1, min(size/10000, runtime.NumCPU())) // do parallel for large arrays - - wait := sync.WaitGroup{} - step := size / routines - for i := 0; i < routines; i++ { - wait.Add(1) - - from := i * step - to := min(from+step, size) - - go func() { - defer wait.Done() - callback(from, to) - }() - } - wait.Wait() -} - -// GenerateDocs creates slice of docs and calls generator for each doc to will it with data -// If timestamp after call is zero, then this function will fill it with deterministic timestamp -// so you could query each doc by range, if needed -func GenerateDocs(size int, generator func(int, *ExampleDoc)) []ExampleDoc { - start := time.Now() - docs := make([]ExampleDoc, size) - splitRange(size, func(from int, to int) { - for i := from; i < to; i++ { - generator(i, &docs[i]) - if docs[i].Timestamp.IsZero() { - docs[i].Timestamp = start.Add(time.Millisecond * time.Duration(i)) - } - } - }) - return docs -} - -// GenerateDocsJSON generates random docs straight in json -// just pass the output to `setup.BulkBytes` to store it -// faster ~2x if additionalFields=false -func GenerateDocsJSON(size int, additionalFields bool) [][]byte { - docs := make([][]byte, size) - splitRange(size, func(from int, to int) { - for i := from; i < to; i++ { - if !additionalFields { - val, err := json.Marshal(RandomDoc(1)) - if err != nil { - panic(err) - } - docs[i] = val - } else { - docs[i] = RandomDocJSON(1, 3) - } - } - }) - return docs -} diff --git a/tests/setup/doc_test.go b/tests/setup/doc_test.go deleted file mode 100644 index 483fd5e4..00000000 --- a/tests/setup/doc_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package setup - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - "lukechampine.com/frand" -) - -func countSize(obj map[string]interface{}) int { - size := len(obj) - for _, v := range obj { - if submap, ok := v.(map[string]interface{}); ok { - size += countSize(submap) - } - } - return size -} - -func TestRandomJSON(t *testing.T) { - for i := 0; i < 10000; i++ { - size := frand.Intn(100) + 1 - str := RandomJSON(size) - res := map[string]interface{}{} - err := json.Unmarshal([]byte(str), &res) - require.NoError(t, err, str) - require.Equal(t, size, countSize(res)) - } -} - -func TestRandomDocJSON(t *testing.T) { - for i := 0; i < 10000; i++ { - str := RandomDocJSON(frand.Intn(10)+1, frand.Intn(10)) - res := map[string]interface{}{} - err := json.Unmarshal(str, &res) - require.NoError(t, err, string(str)) - doc := &ExampleDoc{} - err = json.Unmarshal(str, doc) - require.NoError(t, err, string(str)) - } -} - -func BenchmarkRandomJSON(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - sum := 0 - for pb.Next() { - res := RandomJSON(50) - sum += len(res) - _ = res - } - _ = sum - }) -} - -func BenchmarkRandomDoc(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - sum := 0 - for pb.Next() { - res := RandomDoc(1) - sum += len(res.Message) - _ = res - } - _ = sum - }) -} - -func BenchmarkGenerateDocs(b *testing.B) { - sizes := []int{1000, 10_000, 1_000_000} - - for _, s := range sizes { - b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - var res []ExampleDoc - - for b.Loop() { - res = GenerateDocs(s, func(_ int, doc *ExampleDoc) { - *doc = *RandomDoc(1) - }) - } - - if len(res) == 0 { - b.FailNow() - } - }) - } -} - -func BenchmarkGenerateDocsJSON(b *testing.B) { - sizes := []int{1000, 10_000, 1_000_000} - - for _, s := range sizes { - b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - var res [][]byte - - for b.Loop() { - res = GenerateDocsJSON(s, false) - } - - if len(res) == 0 { - b.FailNow() - } - }) - } -} - -func BenchmarkGenerateDocsJSONFields(b *testing.B) { - sizes := []int{1000, 10_000, 1_000_000} - - for _, s := range sizes { - b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - var res [][]byte - - for b.Loop() { - res = GenerateDocsJSON(s, true) - } - - if len(res) == 0 { - b.FailNow() - } - }) - } -} diff --git a/util/bitmask_test.go b/util/bitmask_test.go index 68026c12..90416f6e 100644 --- a/util/bitmask_test.go +++ b/util/bitmask_test.go @@ -79,7 +79,7 @@ func TestSimpleBitmask(t *testing.T) { assert.True(t, bm.HasBitsIn(maxSize/3*2+8, maxSize-1)) } -func BenchmarkBitmask_HasBitsIn(b *testing.B) { +func BenchmarkBitmask(b *testing.B) { const size = 10000 bm := NewBitmask(size) From 0adc4c4b23878267f7aa0ecd6686a7cce9b1945e Mon Sep 17 00:00:00 2001 From: Banana Duck Date: Mon, 26 Jan 2026 14:19:09 +0300 Subject: [PATCH 5/6] --- tests/setup/doc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/setup/doc.go b/tests/setup/doc.go index 45ce8a3f..3e3d6d00 100644 --- a/tests/setup/doc.go +++ b/tests/setup/doc.go @@ -3,7 +3,6 @@ package setup import ( "encoding/json" "time" - // much faster with multiple goroutines ) // InlineJSON is a string representing valid json From 02517f2db367855d2e87656e561a9be155cbabb1 Mon Sep 17 00:00:00 2001 From: Banana Duck Date: Mon, 26 Jan 2026 14:21:30 +0300 Subject: [PATCH 6/6] --- fracmanager/storage-state.json | 1 - pattern/pattern_test.go | 41 ---------------------------------- 2 files changed, 42 deletions(-) delete mode 100644 fracmanager/storage-state.json diff --git a/fracmanager/storage-state.json b/fracmanager/storage-state.json deleted file mode 100644 index 548d6cf0..00000000 --- a/fracmanager/storage-state.json +++ /dev/null @@ -1 +0,0 @@ -{"capacity_exceeded":false} \ No newline at end of file diff --git a/pattern/pattern_test.go b/pattern/pattern_test.go index 9511cdd5..960b4ea1 100644 --- a/pattern/pattern_test.go +++ b/pattern/pattern_test.go @@ -514,47 +514,6 @@ func TestFindSequence(t *testing.T) { testFindSequence(a, 1, []string{"abc"}, strings.Repeat("ab", 1024)+"c") } -func BenchmarkFindSequence_Deterministic(b *testing.B) { - type testCase struct { - haystack []byte - needles [][]byte - } - - type namedTestCase struct { - name string - cases []testCase - } - - testCases := []namedTestCase{ - { - name: "regular-cases", - cases: []testCase{ - {bb("Hello, world!"), [][]byte{bb("orl")}}, - {bb("some-k8s-service"), [][]byte{bb("k8s")}}, - }, - }, - { - name: "corner-cases", - cases: []testCase{ - {bb(strings.Repeat("ab", 32) + "c"), [][]byte{bb("abc")}}, - {bb(strings.Repeat("ab", 64) + "c"), [][]byte{bb("abc")}}, - {bb(strings.Repeat("ab", 1024) + "c"), [][]byte{bb("abc")}}, - {bb(strings.Repeat("ab", 16384) + "c"), [][]byte{bb("abc")}}, - }, - }, - } - - for _, tc := range testCases { - for i, c := range tc.cases { - b.Run(tc.name+"-"+strconv.Itoa(i), func(b *testing.B) { - for b.Loop() { - findSequence([]byte(c.haystack), c.needles) - } - }) - } - } -} - func BenchmarkFindSequence_Random(b *testing.B) { sizes := []struct { name string