From 5dd37ad836ef1381ce7d31e9efdd68d7aae984b5 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin <valyala@victoriametrics.com> Date: Mon, 22 Jan 2024 23:56:25 +0200 Subject: [PATCH] app/vmselect/netstorage: use []blockRef from blockRefPool in order to reduce memory allocations --- app/vmselect/netstorage/netstorage.go | 39 ++++++++++++++++++++------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/app/vmselect/netstorage/netstorage.go b/app/vmselect/netstorage/netstorage.go index da4468f5ad..f60ad3f67b 100644 --- a/app/vmselect/netstorage/netstorage.go +++ b/app/vmselect/netstorage/netstorage.go @@ -5,10 +5,12 @@ import ( "errors" "flag" "fmt" + "reflect" "sort" "sync" "sync/atomic" "time" + "unsafe" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/searchutils" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage" @@ -1169,8 +1171,7 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin maxSeriesCount := sr.Init(qt, vmstorage.Storage, tfss, tr, sq.MaxMetrics, deadline.Deadline()) indexSearchDuration.UpdateDuration(startTime) type blockRefs struct { - brsPrealloc [4]blockRef - brs []blockRef + brs []blockRef } blocksRead := 0 @@ -1178,7 +1179,7 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin tbf := getTmpBlocksFile() var buf []byte - // metricNamesBuf is used for holding all the loaded unique metric names. + // metricNamesBuf is used for holding all the loaded unique metric names at m and orderedMetricNames. // It should reduce pressure on Go GC by reducing the number of string allocations // when constructing metricName string from byte slice. var metricNamesBuf []byte @@ -1187,6 +1188,10 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin // It should reduce pressure on Go GC by reducing the number of blockRefs allocations. brssPool := make([]blockRefs, 0, maxSeriesCount) + // brsPool is used for holding the most of blockRefs.brs slices across all the loaded time series. + // It should reduce pressure on Go GC by reducing the number of allocations for blockRefs.brs slices. + brsPool := make([]blockRef, 0, maxSeriesCount) + // m maps from metricName to the index of blockRefs inside brssPool m := make(map[string]int, maxSeriesCount) @@ -1224,14 +1229,24 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin brssPool = append(brssPool, blockRefs{}) } brsIdx = len(brssPool) - 1 - brs := &brssPool[brsIdx] - brs.brs = brs.brsPrealloc[:0] } brs := &brssPool[brsIdx] - brs.brs = append(brs.brs, blockRef{ - partRef: br.PartRef(), - addr: addr, - }) + partRef := br.PartRef() + if brs.brs == nil || haveSameBlockRefTails(brs.brs, brsPool) { + // It is safe appending blockRef to brsPool, since there are no other items added there yet. + brsPool = append(brsPool, blockRef{ + partRef: partRef, + addr: addr, + }) + brs.brs = brsPool[len(brsPool)-len(brs.brs)-1 : len(brsPool) : len(brsPool)] + } else { + // It is unsafe appending blockRef to brsPool, since there are other items added there. + // So just append it to brs.brs. + brs.brs = append(brs.brs, blockRef{ + partRef: partRef, + addr: addr, + }) + } if len(brs.brs) == 1 { metricNamesBufLen := len(metricNamesBuf) metricNamesBuf = append(metricNamesBuf, metricName...) @@ -1279,6 +1294,12 @@ type blockRef struct { addr tmpBlockAddr } +func haveSameBlockRefTails(a, b []blockRef) bool { + sha := (*reflect.SliceHeader)(unsafe.Pointer(&a)) + shb := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + return sha.Data+uintptr(sha.Len)*unsafe.Sizeof(blockRef{}) == shb.Data+uintptr(shb.Len)*unsafe.Sizeof(blockRef{}) +} + func setupTfss(qt *querytracer.Tracer, tr storage.TimeRange, tagFilterss [][]storage.TagFilter, maxMetrics int, deadline searchutils.Deadline) ([]*storage.TagFilters, error) { tfss := make([]*storage.TagFilters, 0, len(tagFilterss)) for _, tagFilters := range tagFilterss {