-
Notifications
You must be signed in to change notification settings - Fork 524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ingester memory improvements by adjusting prealloc #4344
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,38 +6,42 @@ package pool | |
|
||
import ( | ||
"sync" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/promauto" | ||
) | ||
|
||
// Pool is a bucketed pool for variably sized byte slices. | ||
var metricAllocOutPool = promauto.NewCounter(prometheus.CounterOpts{ | ||
Namespace: "tempo", | ||
Name: "ingester_prealloc_miss_bytes_total", | ||
Help: "The total number of alloc'ed bytes that missed the sync pools.", | ||
}) | ||
|
||
// Pool is a linearly bucketed pool for variably sized byte slices. | ||
type Pool struct { | ||
buckets []sync.Pool | ||
sizes []int | ||
bktSize int | ||
// make is the function used to create an empty slice when none exist yet. | ||
make func(int) []byte | ||
} | ||
|
||
// New returns a new Pool with size buckets for minSize to maxSize | ||
// increasing by the given factor. | ||
func New(minSize, maxSize int, factor float64, makeFunc func(int) []byte) *Pool { | ||
if minSize < 1 { | ||
panic("invalid minimum pool size") | ||
} | ||
func New(maxSize, bktSize int, makeFunc func(int) []byte) *Pool { | ||
if maxSize < 1 { | ||
panic("invalid maximum pool size") | ||
} | ||
if factor < 1 { | ||
if bktSize < 1 { | ||
panic("invalid factor") | ||
} | ||
|
||
var sizes []int | ||
|
||
for s := minSize; s <= maxSize; s = int(float64(s) * factor) { | ||
sizes = append(sizes, s) | ||
if maxSize%bktSize != 0 { | ||
panic("invalid bucket size") | ||
} | ||
|
||
bkts := maxSize / bktSize | ||
|
||
p := &Pool{ | ||
buckets: make([]sync.Pool, len(sizes)), | ||
sizes: sizes, | ||
buckets: make([]sync.Pool, bkts), | ||
bktSize: bktSize, | ||
make: makeFunc, | ||
} | ||
|
||
|
@@ -46,29 +50,40 @@ func New(minSize, maxSize int, factor float64, makeFunc func(int) []byte) *Pool | |
|
||
// Get returns a new byte slices that fits the given size. | ||
func (p *Pool) Get(sz int) []byte { | ||
for i, bktSize := range p.sizes { | ||
if sz > bktSize { | ||
continue | ||
} | ||
b := p.buckets[i].Get() | ||
if b == nil { | ||
b = p.make(bktSize) | ||
} | ||
return b.([]byte) | ||
if sz < 0 { | ||
sz = 0 // just panic? | ||
} | ||
return p.make(sz) | ||
|
||
// Find the right bucket. | ||
bkt := sz / p.bktSize | ||
|
||
if bkt >= len(p.buckets) { | ||
metricAllocOutPool.Add(float64(sz)) // track the number of bytes alloc'ed outside the pool for future tuning | ||
return p.make(sz) | ||
} | ||
|
||
b := p.buckets[bkt].Get() | ||
if b == nil { | ||
sz := (bkt + 1) * p.bktSize | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this alloc, or is the compiler smart enough to see its only used once? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you're really testing my knowledge of compilers :). my guess is this value would never leave a register There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can do escape analysis in Go with |
||
b = p.make(sz) | ||
} | ||
return b.([]byte) | ||
} | ||
|
||
// Put adds a slice to the right bucket in the pool. This method has been adjusted from its initial | ||
// implementation to ignore byte slices that dont have the correct size | ||
// Put adds a slice to the right bucket in the pool. | ||
func (p *Pool) Put(s []byte) { | ||
c := cap(s) | ||
for i, size := range p.sizes { | ||
if c == size { | ||
p.buckets[i].Put(s) // nolint: staticcheck | ||
} | ||
if c < size { | ||
return | ||
} | ||
|
||
if c%p.bktSize != 0 { | ||
return | ||
} | ||
bkt := (c / p.bktSize) - 1 | ||
if bkt < 0 { | ||
return | ||
} | ||
if bkt >= len(p.buckets) { | ||
return | ||
} | ||
|
||
p.buckets[bkt].Put(s) // nolint: staticcheck | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose its a trad-off here to alloc or do the math twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't really "alloc" in any way we are concerned about. we're always conscious of allocating memory that escapes to the heap. everything else is cheap. whether this is stored on the stack of just stays in a register it would likely be undetectable from a tempo perf standpoint.