/*
 *
 * 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 mem import ( ) 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 []int sizedPools []bufferPool fallbackPool bufferPool maxPoolCap 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.Pool defaultSize int shouldZero 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 []*sizedBufferPool fallbackPool 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.Pool shouldZero 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() }