Repository: grpc/grpc-go
File: mem/buffers.go (Go)

Code before:
	// Len returns the Buffer's size.
	Len() int

	split(n int) (left, right Buffer)
	read(buf []byte) (int, Buffer)
}

var (
	bufferPoolingThreshold = 1 << 10

	bufferObjectPool = sync.Pool{New: func() any { return new(buffer) }}
)

// IsBelowBufferPoolingThreshold returns true if the given size is less than or
// equal to the threshold for buffer pooling. This is used to determine whether
// to pool buffers or allocate them directly.
func IsBelowBufferPoolingThreshold(size int) bool {
	return size <= bufferPoolingThreshold
}

type atomicInt32 struct {
	atomic.Int32
	// initialized enables sanity checks without the overhead of atomic
	// operations. This field is not safe for concurrent access and is used in a
	// best-effort manner for assertion purposes only. It does not play a role
	// in the concurrent logic of reference counting.
	initialized bool
}

type buffer struct {
	refs atomicInt32
	data []byte

	// rootBuf is the buffer responsible for returning origData to the pool
	// once the reference count drops to 0.
	//
	// When a buffer is split, the new buffer inherits the rootBuf of the
	// original and increments the root's reference count. For the
	// initial buffer (the root), this field points to itself.
	rootBuf *buffer

	// The following fields are only set for root buffers.
	origData *[]byte
	pool     BufferPool
}

func newBuffer() *buffer {
	return bufferObjectPool.Get().(*buffer)
}

// NewBuffer creates a new Buffer from the given data, initializing the reference

Review comment (performance):
Couple of things here:
- The `Buffer` interface does that a buffer is not safe for concurrent access. Given that, do we need this to be mentioned here?
- Do you have an idea of how much overhead the atomic operation of checking if the ref count is zero causes? The reason I'm asking is because this new field (and the checks associated with it) are sprinkled across multiple methods and I'm wondering if the code complexity (and the maintenance costs) are worth it?

Code after:
	// Len returns the Buffer's size.
	Len() int

	split(n int) (left, right Buffer)
	read(buf []byte) (int, Buffer)
}

var (
	bufferPoolingThreshold = 1 << 10

	bufferObjectPool = sync.Pool{New: func() any { return new(buffer) }}
)

// IsBelowBufferPoolingThreshold returns true if the given size is less than or
// equal to the threshold for buffer pooling. This is used to determine whether
// to pool buffers or allocate them directly.
func IsBelowBufferPoolingThreshold(size int) bool {
	return size <= bufferPoolingThreshold
}

type buffer struct {
	refs atomic.Int32
	data []byte

	// rootBuf is the buffer responsible for returning origData to the pool
	// once the reference count drops to 0.
	//
	// When a buffer is split, the new buffer inherits the rootBuf of the
	// original and increments the root's reference count. For the
	// initial buffer (the root), this field points to itself.
	rootBuf *buffer

	// The following fields are only set for root buffers.
	origData *[]byte
	pool     BufferPool
}

func newBuffer() *buffer {
	return bufferObjectPool.Get().(*buffer)
}

// NewBuffer creates a new Buffer from the given data, initializing the reference
// counter to 1. The data will then be returned to the given pool when all
// references to the returned Buffer are released. As a special case to avoid
// additional allocations, if the given buffer pool is nil, the returned buffer
// will be a "no-op" Buffer where invoking Buffer.Free() does nothing and the
// underlying data is never freed.
//
// Note that the backing array of the given data is not copied.
func NewBuffer(data *[]byte, pool BufferPool) Buffer {
	// Use the buffer's capacity instead of the length, otherwise buffers may