Skip to content

custom-buffer-pool analysis - #35

Draft
raj-prince wants to merge 2 commits into
mainfrom
custom_buffer_pool
Draft

custom-buffer-pool analysis#35
raj-prince wants to merge 2 commits into
mainfrom
custom_buffer_pool

Conversation

@raj-prince

Copy link
Copy Markdown
Owner

No description provided.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a custom sharded, memory-mapped buffer pool (gomem) to optimize gRPC client performance under high concurrency, along with associated benchmarks, tests, and documentation. Feedback on these changes highlights critical issues, including the need for platform-specific build constraints to prevent compilation failures on unsupported platforms, and a potential race condition leading to resource leaks during concurrent arena allocations. Additionally, improvements are suggested to avoid pool pollution in SimpleBufferPool by discarding too-small buffers, optimize the double-free protection scan to reduce lock contention, and prevent slice capacity degradation during slot compaction.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread gomem_pool.go
@@ -0,0 +1,568 @@
// Copyright 2026 Google LLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The custom memory-mapped buffer pool uses platform-specific assembly/syscalls (such as syscall.RawSyscall(309, ...) for SYS_GETCPU on Linux x86_64) and unexported runtime linknames (runtime.procPin). Without build tags, this file will fail to compile or crash on non-Linux or non-x86_64 platforms (e.g., macOS, Windows, ARM64). Add a build constraint to restrict this file to supported platforms.

Suggested change
// Copyright 2026 Google LLC
//go:build linux && amd64
// Copyright 2026 Google LLC

Comment thread gomem_pool.go
Comment on lines +88 to +90
if ok {
p.pool.Put(bs)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In SimpleBufferPool.Get, when a buffer retrieved from the pool is too small (cap(*bs) < size), putting it back into the pool (p.pool.Put(bs)) causes pool pollution. In subsequent calls, p.pool.Get() will repeatedly return this same too-small buffer, reject it, put it back, and allocate a new one on the heap. This leads to an infinite loop of pool pollution and forces constant heap allocations for larger requests. Discard the too-small buffer instead so it can be garbage collected.

Comment thread gomem_pool.go
_ = procPin()
}

if len(shard.freeSlots)-shard.head == 0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When a shard runs out of slots, the lock is released (shard.mu.Unlock()) and the goroutine unpins to perform rawMmap safely. Under high concurrency, multiple goroutines can concurrently find the shard empty, release the lock, allocate a new arena, and then blindly append it to the shard without double-checking if another thread already allocated an arena. This leads to a massive over-allocation of arenas (resource leak/bloat) under high load.

To fix this, double-check len(shard.freeSlots) - shard.head == 0 after re-acquiring the lock. If it is no longer empty, either free the newly allocated arena or keep it in a spare list instead of blindly appending it.

Comment thread gomem_pool.go
Comment on lines +318 to +324
for i := shard.head; i < len(shard.freeSlots); i++ {
if shard.freeSlots[i] == slotPacked {
shard.mu.Unlock()
*bufPtr = nil
return // Ignore duplicate free
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The double-free protection performs a linear scan over shard.freeSlots from shard.head to len(shard.freeSlots) under the shard's mutex lock. Under high concurrency and large pool sizes, this $O(N)$ scan will severely bottleneck the Put path and increase lock contention. Consider using a $O(1)$ state tracking mechanism (such as a bitmask or a boolean array per slot in the Arena struct) if double-free protection is strictly required, or removing the scan if the caller is guaranteed to be well-behaved.

Comment thread gomem_pool.go
Comment on lines +284 to +287
if shard.head > 128 {
shard.freeSlots = shard.freeSlots[shard.head:]
shard.head = 0
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Reslicing shard.freeSlots = shard.freeSlots[shard.head:] shifts the slice's start pointer and decreases its capacity (cap). As head grows and compaction is triggered, the capacity of freeSlots continually shrinks. When new elements are appended later, it will frequently trigger new underlying array allocations and copies. Use copy to shift the elements to the beginning of the slice to preserve the capacity.

Suggested change
if shard.head > 128 {
shard.freeSlots = shard.freeSlots[shard.head:]
shard.head = 0
}
if shard.head > 128 {
copy(shard.freeSlots, shard.freeSlots[shard.head:])
shard.freeSlots = shard.freeSlots[:len(shard.freeSlots)-shard.head]
shard.head = 0
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant