Source File
buffer_pool.go
Belonging Package
google.golang.org/grpc/internal/mem
/*** Copyright 2026 gRPC authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.**/// Package mem provides utilities that facilitate memory reuse in byte slices// that are used as buffers.package memimport ()const (goPageSize = 4 * 1024 // 4KiB. N.B. this must be a power of 2.)var uintSize = bits.UintSize // use a variable for mocking during tests.// bufferPool is a copy of the public bufferPool interface used to avoid// circular dependencies.type bufferPool interface {// Get returns a buffer with specified length from the pool.Get(length int) *[]byte// Put returns a buffer to the pool.//// The provided pointer must hold a prefix of the buffer obtained via// BufferPool.Get to ensure the buffer's entire capacity can be re-used.Put(*[]byte)}// BinaryTieredBufferPool is a buffer pool that uses multiple sub-pools with// power-of-two sizes.type BinaryTieredBufferPool struct {// exponentToNextLargestPoolMap maps a power-of-two exponent (e.g., 12 for// 4KB) to the index of the next largest sizedBufferPool. This is used by// Get() to find the smallest pool that can satisfy a request for a given// size.exponentToNextLargestPoolMap []int// exponentToPreviousLargestPoolMap maps a power-of-two exponent to the// index of the previous largest sizedBufferPool. This is used by Put()// to return a buffer to the most appropriate pool based on its capacity.exponentToPreviousLargestPoolMap []intsizedPools []bufferPoolfallbackPool bufferPoolmaxPoolCap int // Optimization: Cache max capacity}// NewBinaryTieredBufferPool returns a BufferPool backed by multiple sub-pools.// This structure enables O(1) lookup time for Get and Put operations.//// The arguments provided are the exponents for the buffer capacities (powers// of 2), not the raw byte sizes. For example, to create a pool of 16KB buffers// (2^14 bytes), pass 14 as the argument.func ( ...uint8) (*BinaryTieredBufferPool, error) {return newBinaryTiered(func( int) bufferPool {return newSizedBufferPool(, true)}, &SimpleBufferPool{shouldZero: true}, ...)}// NewDirtyBinaryTieredBufferPool returns a BufferPool backed by multiple// sub-pools. It is similar to NewBinaryTieredBufferPool but it does not// initialize the buffers before returning them.func ( ...uint8) (*BinaryTieredBufferPool, error) {return newBinaryTiered(func( int) bufferPool {return newSizedBufferPool(, false)}, NewDirtySimplePool(), ...)}func ( func(int) bufferPool, bufferPool, ...uint8) (*BinaryTieredBufferPool, error) {slices.Sort()= slices.Compact()// Determine the maximum exponent we need to support. This depends on the// word size (32-bit vs 64-bit).:= uintSize - 2:= slices.Repeat([]int{-1}, +1):= slices.Repeat([]int{-1}, +1):= 0:= make([]bufferPool, 0, len())for , := range {// Allocating slices of size > 2^maxExponent isn't possible on// maxExponent-bit machines.if int() > {return nil, fmt.Errorf("mem: allocating slice of size 2^%d is not possible", )}:= 1 <<= append(, ())= max(, )// Map the exact power of 2 to this pool index.[] =[] =}// Fill gaps for Get() (Next Largest)// We iterate backwards. If current is empty, take the value from the right (larger).for := - 1; >= 0; -- {if [] == -1 {[] = [+1]}}// Fill gaps for Put() (Previous Largest)// We iterate forwards. If current is empty, take the value from the left (smaller).for := 1; <= ; ++ {if [] == -1 {[] = [-1]}}return &BinaryTieredBufferPool{exponentToNextLargestPoolMap: ,exponentToPreviousLargestPoolMap: ,sizedPools: ,maxPoolCap: ,fallbackPool: ,}, nil}// Get returns a buffer with specified length from the pool.func ( *BinaryTieredBufferPool) ( int) *[]byte {return .poolForGet().Get()}func ( *BinaryTieredBufferPool) ( int) bufferPool {if == 0 || > .maxPoolCap {return .fallbackPool}// Calculate the exponent of the smallest power of 2 >= size.// We subtract 1 from size to handle exact powers of 2 correctly.//// Examples:// size=16 (0b10000) -> size-1=15 (0b01111) -> bits.Len=4 -> Pool for 2^4// size=17 (0b10001) -> size-1=16 (0b10000) -> bits.Len=5 -> Pool for 2^5:= uint( - 1):= .exponentToNextLargestPoolMap[bits.Len()]return .sizedPools[]}// Put returns a buffer to the pool.func ( *BinaryTieredBufferPool) ( *[]byte) {// We pass the capacity of the buffer, and not the size of the buffer here.// If we did the latter, all buffers would eventually move to the smallest// pool..poolForPut(cap(*)).Put()}func ( *BinaryTieredBufferPool) ( int) bufferPool {if == 0 {return NopBufferPool{}}if > .maxPoolCap {return .fallbackPool}// Find the pool with the largest capacity <= bCap.//// We calculate the exponent of the largest power of 2 <= bCap.// bits.Len(x) returns the minimum number of bits required to represent x;// i.e. the number of bits up to and including the most significant bit.// Subtracting 1 gives the 0-based index of the most significant bit,// which is the exponent of the largest power of 2 <= bCap.//// Examples:// cap=16 (0b10000) -> Len=5 -> 5-1=4 -> 2^4// cap=15 (0b01111) -> Len=4 -> 4-1=3 -> 2^3:= bits.Len(uint()) - 1:= .exponentToPreviousLargestPoolMap[]// The buffer is smaller than the smallest power of 2, discard it.if == -1 {// Buffer is smaller than our smallest pool bucket.return NopBufferPool{}}return .sizedPools[]}// NopBufferPool is a buffer pool that returns new buffers without pooling.type NopBufferPool struct{}// Get returns a buffer with specified length from the pool.func (NopBufferPool) ( int) *[]byte {:= make([]byte, )return &}// Put returns a buffer to the pool.func (NopBufferPool) (*[]byte) {}// sizedBufferPool is a BufferPool implementation that is optimized for specific// buffer sizes. For example, HTTP/2 frames within gRPC have a default max size// of 16kb and a sizedBufferPool can be configured to only return buffers with a// capacity of 16kb. Note that however it does not support returning larger// buffers and in fact panics if such a buffer is requested. Because of this,// this BufferPool implementation is not meant to be used on its own and rather// is intended to be embedded in a TieredBufferPool such that Get is only// invoked when the required size is smaller than or equal to defaultSize.type sizedBufferPool struct {pool sync.PooldefaultSize intshouldZero bool}func ( *sizedBufferPool) ( int) *[]byte {, := .pool.Get().(*[]byte)if ! {:= make([]byte, , .defaultSize)return &}:= *if .shouldZero {clear([:cap()])}* = [:]return}func ( *sizedBufferPool) ( *[]byte) {if cap(*) < .defaultSize {// Ignore buffers that are too small to fit in the pool. Otherwise, when// Get is called it will panic as it tries to index outside the bounds// of the buffer.return}.pool.Put()}func ( int, bool) *sizedBufferPool {return &sizedBufferPool{defaultSize: ,shouldZero: ,}}// TieredBufferPool implements the BufferPool interface with multiple tiers of// buffer pools for different sizes of buffers.type TieredBufferPool struct {sizedPools []*sizedBufferPoolfallbackPool SimpleBufferPool}// NewTieredBufferPool returns a BufferPool implementation that uses multiple// underlying pools of the given pool sizes.func ( ...int) *TieredBufferPool {sort.Ints():= make([]*sizedBufferPool, len())for , := range {[] = newSizedBufferPool(, true)}return &TieredBufferPool{sizedPools: ,fallbackPool: SimpleBufferPool{shouldZero: true},}}// Get returns a buffer with specified length from the pool.func ( *TieredBufferPool) ( int) *[]byte {return .getPool().Get()}// Put returns a buffer to the pool.func ( *TieredBufferPool) ( *[]byte) {.getPool(cap(*)).Put()}func ( *TieredBufferPool) ( int) bufferPool {:= sort.Search(len(.sizedPools), func( int) bool {return .sizedPools[].defaultSize >=})if == len(.sizedPools) {return &.fallbackPool}return .sizedPools[]}// SimpleBufferPool is an implementation of the mem.BufferPool interface that// attempts to pool buffers with a sync.Pool. When Get is invoked, it tries to// acquire a buffer from the pool but if that buffer is too small, it returns it// to the pool and creates a new one.type SimpleBufferPool struct {pool sync.PoolshouldZero bool}// NewDirtySimplePool constructs a [SimpleBufferPool]. It does not initialize// the buffers before returning them. Callers must ensure they don't read the// buffers before writing data to them.func () *SimpleBufferPool {return &SimpleBufferPool{shouldZero: false,}}// Get returns a buffer with specified length from the pool.func ( *SimpleBufferPool) ( int) *[]byte {, := .pool.Get().(*[]byte)if && cap(*) >= {if .shouldZero {clear((*)[:cap(*)])}* = (*)[:]return}// A buffer was pulled from the pool, but it is too small. Put it back in// the pool and create one large enough.if {.pool.Put()}// If we're going to allocate, round up to the nearest page. This way if// requests frequently arrive with small variation we don't allocate// repeatedly if we get unlucky and they increase over time. By default we// only allocate here if size > 1MiB. Because goPageSize is a power of 2, we// can round up efficiently.:= ( + goPageSize - 1) & ^(goPageSize - 1):= make([]byte, , )return &}// Put returns a buffer to the pool.func ( *SimpleBufferPool) ( *[]byte) {.pool.Put()}
The pages are generated with Golds v0.8.4. (GOOS=linux GOARCH=amd64)