blob: f211e727451f212525781b1b5fbe75c411ffdac3 [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001/*
2 *
3 * Copyright 2024 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19package mem
20
21import (
22 "sort"
23 "sync"
24
25 "google.golang.org/grpc/internal"
26)
27
28// BufferPool is a pool of buffers that can be shared and reused, resulting in
29// decreased memory allocation.
30type BufferPool interface {
31 // Get returns a buffer with specified length from the pool.
32 Get(length int) *[]byte
33
34 // Put returns a buffer to the pool.
bseeniva0b9cbcb2026-02-12 19:11:11 +053035 //
36 // The provided pointer must hold a prefix of the buffer obtained via
37 // BufferPool.Get to ensure the buffer's entire capacity can be re-used.
Abhay Kumara61c5222025-11-10 07:32:50 +000038 Put(*[]byte)
39}
40
41var defaultBufferPoolSizes = []int{
42 256,
43 4 << 10, // 4KB (go page size)
44 16 << 10, // 16KB (max HTTP/2 frame size used by gRPC)
45 32 << 10, // 32KB (default buffer size for io.Copy)
46 1 << 20, // 1MB
47}
48
49var defaultBufferPool BufferPool
50
51func init() {
52 defaultBufferPool = NewTieredBufferPool(defaultBufferPoolSizes...)
53
54 internal.SetDefaultBufferPoolForTesting = func(pool BufferPool) {
55 defaultBufferPool = pool
56 }
57
58 internal.SetBufferPoolingThresholdForTesting = func(threshold int) {
59 bufferPoolingThreshold = threshold
60 }
61}
62
63// DefaultBufferPool returns the current default buffer pool. It is a BufferPool
64// created with NewBufferPool that uses a set of default sizes optimized for
65// expected workflows.
66func DefaultBufferPool() BufferPool {
67 return defaultBufferPool
68}
69
70// NewTieredBufferPool returns a BufferPool implementation that uses multiple
71// underlying pools of the given pool sizes.
72func NewTieredBufferPool(poolSizes ...int) BufferPool {
73 sort.Ints(poolSizes)
74 pools := make([]*sizedBufferPool, len(poolSizes))
75 for i, s := range poolSizes {
76 pools[i] = newSizedBufferPool(s)
77 }
78 return &tieredBufferPool{
79 sizedPools: pools,
80 }
81}
82
83// tieredBufferPool implements the BufferPool interface with multiple tiers of
84// buffer pools for different sizes of buffers.
85type tieredBufferPool struct {
86 sizedPools []*sizedBufferPool
87 fallbackPool simpleBufferPool
88}
89
90func (p *tieredBufferPool) Get(size int) *[]byte {
91 return p.getPool(size).Get(size)
92}
93
94func (p *tieredBufferPool) Put(buf *[]byte) {
95 p.getPool(cap(*buf)).Put(buf)
96}
97
98func (p *tieredBufferPool) getPool(size int) BufferPool {
99 poolIdx := sort.Search(len(p.sizedPools), func(i int) bool {
100 return p.sizedPools[i].defaultSize >= size
101 })
102
103 if poolIdx == len(p.sizedPools) {
104 return &p.fallbackPool
105 }
106
107 return p.sizedPools[poolIdx]
108}
109
110// sizedBufferPool is a BufferPool implementation that is optimized for specific
111// buffer sizes. For example, HTTP/2 frames within gRPC have a default max size
112// of 16kb and a sizedBufferPool can be configured to only return buffers with a
113// capacity of 16kb. Note that however it does not support returning larger
114// buffers and in fact panics if such a buffer is requested. Because of this,
115// this BufferPool implementation is not meant to be used on its own and rather
116// is intended to be embedded in a tieredBufferPool such that Get is only
117// invoked when the required size is smaller than or equal to defaultSize.
118type sizedBufferPool struct {
119 pool sync.Pool
120 defaultSize int
121}
122
123func (p *sizedBufferPool) Get(size int) *[]byte {
bseeniva0b9cbcb2026-02-12 19:11:11 +0530124 buf, ok := p.pool.Get().(*[]byte)
125 if !ok {
126 buf := make([]byte, size, p.defaultSize)
127 return &buf
128 }
Abhay Kumara61c5222025-11-10 07:32:50 +0000129 b := *buf
130 clear(b[:cap(b)])
131 *buf = b[:size]
132 return buf
133}
134
135func (p *sizedBufferPool) Put(buf *[]byte) {
136 if cap(*buf) < p.defaultSize {
137 // Ignore buffers that are too small to fit in the pool. Otherwise, when
138 // Get is called it will panic as it tries to index outside the bounds
139 // of the buffer.
140 return
141 }
142 p.pool.Put(buf)
143}
144
145func newSizedBufferPool(size int) *sizedBufferPool {
146 return &sizedBufferPool{
Abhay Kumara61c5222025-11-10 07:32:50 +0000147 defaultSize: size,
148 }
149}
150
151var _ BufferPool = (*simpleBufferPool)(nil)
152
153// simpleBufferPool is an implementation of the BufferPool interface that
154// attempts to pool buffers with a sync.Pool. When Get is invoked, it tries to
155// acquire a buffer from the pool but if that buffer is too small, it returns it
156// to the pool and creates a new one.
157type simpleBufferPool struct {
158 pool sync.Pool
159}
160
161func (p *simpleBufferPool) Get(size int) *[]byte {
162 bs, ok := p.pool.Get().(*[]byte)
163 if ok && cap(*bs) >= size {
bseeniva0b9cbcb2026-02-12 19:11:11 +0530164 clear((*bs)[:cap(*bs)])
Abhay Kumara61c5222025-11-10 07:32:50 +0000165 *bs = (*bs)[:size]
166 return bs
167 }
168
169 // A buffer was pulled from the pool, but it is too small. Put it back in
170 // the pool and create one large enough.
171 if ok {
172 p.pool.Put(bs)
173 }
174
175 b := make([]byte, size)
176 return &b
177}
178
179func (p *simpleBufferPool) Put(buf *[]byte) {
180 p.pool.Put(buf)
181}
182
183var _ BufferPool = NopBufferPool{}
184
185// NopBufferPool is a buffer pool that returns new buffers without pooling.
186type NopBufferPool struct{}
187
188// Get returns a buffer with specified length from the pool.
189func (NopBufferPool) Get(length int) *[]byte {
190 b := make([]byte, length)
191 return &b
192}
193
194// Put returns a buffer to the pool.
195func (NopBufferPool) Put(*[]byte) {
196}