google.golang.org/grpc@v1.72.2/mem/buffer_pool.go (about) 1 /* 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 19 package mem 20 21 import ( 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. 30 type 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. 35 Put(*[]byte) 36 } 37 38 var defaultBufferPoolSizes = []int{ 39 256, 40 4 << 10, // 4KB (go page size) 41 16 << 10, // 16KB (max HTTP/2 frame size used by gRPC) 42 32 << 10, // 32KB (default buffer size for io.Copy) 43 1 << 20, // 1MB 44 } 45 46 var defaultBufferPool BufferPool 47 48 func init() { 49 defaultBufferPool = NewTieredBufferPool(defaultBufferPoolSizes...) 50 51 internal.SetDefaultBufferPoolForTesting = func(pool BufferPool) { 52 defaultBufferPool = pool 53 } 54 55 internal.SetBufferPoolingThresholdForTesting = func(threshold int) { 56 bufferPoolingThreshold = threshold 57 } 58 } 59 60 // DefaultBufferPool returns the current default buffer pool. It is a BufferPool 61 // created with NewBufferPool that uses a set of default sizes optimized for 62 // expected workflows. 63 func DefaultBufferPool() BufferPool { 64 return defaultBufferPool 65 } 66 67 // NewTieredBufferPool returns a BufferPool implementation that uses multiple 68 // underlying pools of the given pool sizes. 69 func NewTieredBufferPool(poolSizes ...int) BufferPool { 70 sort.Ints(poolSizes) 71 pools := make([]*sizedBufferPool, len(poolSizes)) 72 for i, s := range poolSizes { 73 pools[i] = newSizedBufferPool(s) 74 } 75 return &tieredBufferPool{ 76 sizedPools: pools, 77 } 78 } 79 80 // tieredBufferPool implements the BufferPool interface with multiple tiers of 81 // buffer pools for different sizes of buffers. 82 type tieredBufferPool struct { 83 sizedPools []*sizedBufferPool 84 fallbackPool simpleBufferPool 85 } 86 87 func (p *tieredBufferPool) Get(size int) *[]byte { 88 return p.getPool(size).Get(size) 89 } 90 91 func (p *tieredBufferPool) Put(buf *[]byte) { 92 p.getPool(cap(*buf)).Put(buf) 93 } 94 95 func (p *tieredBufferPool) getPool(size int) BufferPool { 96 poolIdx := sort.Search(len(p.sizedPools), func(i int) bool { 97 return p.sizedPools[i].defaultSize >= size 98 }) 99 100 if poolIdx == len(p.sizedPools) { 101 return &p.fallbackPool 102 } 103 104 return p.sizedPools[poolIdx] 105 } 106 107 // sizedBufferPool is a BufferPool implementation that is optimized for specific 108 // buffer sizes. For example, HTTP/2 frames within gRPC have a default max size 109 // of 16kb and a sizedBufferPool can be configured to only return buffers with a 110 // capacity of 16kb. Note that however it does not support returning larger 111 // buffers and in fact panics if such a buffer is requested. Because of this, 112 // this BufferPool implementation is not meant to be used on its own and rather 113 // is intended to be embedded in a tieredBufferPool such that Get is only 114 // invoked when the required size is smaller than or equal to defaultSize. 115 type sizedBufferPool struct { 116 pool sync.Pool 117 defaultSize int 118 } 119 120 func (p *sizedBufferPool) Get(size int) *[]byte { 121 buf := p.pool.Get().(*[]byte) 122 b := *buf 123 clear(b[:cap(b)]) 124 *buf = b[:size] 125 return buf 126 } 127 128 func (p *sizedBufferPool) Put(buf *[]byte) { 129 if cap(*buf) < p.defaultSize { 130 // Ignore buffers that are too small to fit in the pool. Otherwise, when 131 // Get is called it will panic as it tries to index outside the bounds 132 // of the buffer. 133 return 134 } 135 p.pool.Put(buf) 136 } 137 138 func newSizedBufferPool(size int) *sizedBufferPool { 139 return &sizedBufferPool{ 140 pool: sync.Pool{ 141 New: func() any { 142 buf := make([]byte, size) 143 return &buf 144 }, 145 }, 146 defaultSize: size, 147 } 148 } 149 150 var _ BufferPool = (*simpleBufferPool)(nil) 151 152 // simpleBufferPool is an implementation of the BufferPool interface that 153 // attempts to pool buffers with a sync.Pool. When Get is invoked, it tries to 154 // acquire a buffer from the pool but if that buffer is too small, it returns it 155 // to the pool and creates a new one. 156 type simpleBufferPool struct { 157 pool sync.Pool 158 } 159 160 func (p *simpleBufferPool) Get(size int) *[]byte { 161 bs, ok := p.pool.Get().(*[]byte) 162 if ok && cap(*bs) >= size { 163 *bs = (*bs)[:size] 164 return bs 165 } 166 167 // A buffer was pulled from the pool, but it is too small. Put it back in 168 // the pool and create one large enough. 169 if ok { 170 p.pool.Put(bs) 171 } 172 173 b := make([]byte, size) 174 return &b 175 } 176 177 func (p *simpleBufferPool) Put(buf *[]byte) { 178 p.pool.Put(buf) 179 } 180 181 var _ BufferPool = NopBufferPool{} 182 183 // NopBufferPool is a buffer pool that returns new buffers without pooling. 184 type NopBufferPool struct{} 185 186 // Get returns a buffer with specified length from the pool. 187 func (NopBufferPool) Get(length int) *[]byte { 188 b := make([]byte, length) 189 return &b 190 } 191 192 // Put returns a buffer to the pool. 193 func (NopBufferPool) Put(*[]byte) { 194 }