Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mem: introduce mem package to facilitate memory reuse #7432

Merged
merged 15 commits into from
Aug 1, 2024

Conversation

PapaCharlie
Copy link
Contributor

@PapaCharlie PapaCharlie commented Jul 22, 2024

This package is required for #7356 but is introduced early here to allow additional features to be built on top of it. It provides a mechanism to reuse and recycle buffers to reduce GC pressure.

RELEASE NOTES:

  • TBD

Copy link

linux-foundation-easycla bot commented Jul 22, 2024

CLA Signed


The committers listed above are authorized under a signed CLA.

Copy link

codecov bot commented Jul 22, 2024

Codecov Report

Attention: Patch coverage is 88.88889% with 18 lines in your changes missing coverage. Please review.

Project coverage is 81.57%. Comparing base (6fa393c) to head (aaca195).

Files Patch % Lines
mem/buffer_slice.go 85.93% 8 Missing and 1 partial ⚠️
mem/buffer_pool.go 90.56% 2 Missing and 3 partials ⚠️
mem/buffers.go 91.11% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7432      +/-   ##
==========================================
+ Coverage   81.38%   81.57%   +0.19%     
==========================================
  Files         354      357       +3     
  Lines       27080    27242     +162     
==========================================
+ Hits        22040    22224     +184     
+ Misses       3831     3817      -14     
+ Partials     1209     1201       -8     
Files Coverage Δ
mem/buffers.go 91.11% <91.11%> (ø)
mem/buffer_pool.go 90.56% <90.56%> (ø)
mem/buffer_slice.go 85.93% <85.93%> (ø)

... and 23 files with indirect coverage changes

@easwars easwars requested review from easwars and dfawley July 22, 2024 18:14
@easwars easwars self-assigned this Jul 22, 2024
@easwars easwars added the Type: Feature New features or improvements in behavior label Jul 22, 2024
@easwars easwars added this to the 1.66 Release milestone Jul 22, 2024
@arvindbr8
Copy link
Member

cc: @printchard

mem/buffers_test.go Outdated Show resolved Hide resolved
@easwars easwars changed the title Introduce mem package mem: introduce mem package to facilitate memory reuse Jul 22, 2024
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
go.mod Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers.go Outdated Show resolved Hide resolved
mem/buffers_test.go Outdated Show resolved Hide resolved
mem/buffers_test.go Outdated Show resolved Hide resolved
mem/buffer_slice.go Show resolved Hide resolved
mem/buffer_slice.go Outdated Show resolved Hide resolved
mem/buffer_slice.go Show resolved Hide resolved
mem/buffer_slice.go Outdated Show resolved Hide resolved
mem/buffer_slice.go Outdated Show resolved Hide resolved
mem/buffer_slice.go Outdated Show resolved Hide resolved
mem/buffer_slice.go Show resolved Hide resolved
mem/buffer_slice.go Outdated Show resolved Hide resolved
mem/buffer_pool.go Outdated Show resolved Hide resolved
mem/buffer_pool.go Outdated Show resolved Hide resolved
1 << 20, // 1MB
}

var defaultBufferPool = func() *atomic.Pointer[BufferPool] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be better to declare a var defaultBufferPool *atomic.Pointer[BufferPool] and initialize it in an init function?

Copy link
Member

Choose a reason for hiding this comment

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

Since it's not exported I'm not too picky either way, but I agree that might be nicer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, went ahead and changed it along with removing the atomic.

mem/buffer_pool.go Outdated Show resolved Hide resolved
mem/buffer_pool.go Outdated Show resolved Hide resolved
mem/buffer_pool.go Outdated Show resolved Hide resolved
mem/buffer_pool.go Outdated Show resolved Hide resolved
mem/buffer_pool.go Show resolved Hide resolved
This package is required for grpc#7356 but is
introduced early here to allow additional features to be built on top of it. It
provides a mechanism to reuse and recycle buffers to reduce GC pressure.
@easwars
Copy link
Contributor

easwars commented Jul 23, 2024

@PapaCharlie : Could you please reply to individual comments with how you addressed them instead of marking them as resolved, since the latter approach means that the reviewer has to open all the resolved comments to see what that comment was in the first place and whether it was addressed to their satisfaction. Thanks.

@easwars easwars assigned PapaCharlie and unassigned easwars Jul 23, 2024
@PapaCharlie
Copy link
Contributor Author

Ah sorry about that. I did reply, but I didn't publish them since I was in review mode still.. I just published my replies

@easwars
Copy link
Contributor

easwars commented Jul 25, 2024

I've pushed a few more commits with more tests and in the process of writing them, I found a bug in the Reader and also fixed it. PTAL.

@easwars
Copy link
Contributor

easwars commented Jul 25, 2024

At this point, I'm mostly OK with how this package looks.

@PapaCharlie : Could you please take a look at my commits to see if you are Ok with those changes.

@dfawley : Could you make a pass. Thanks.

@easwars
Copy link
Contributor

easwars commented Jul 25, 2024

This one comment is still unaddressed though: #7432 (comment)

I don't have a strong opinion there, but I was wondering if one is a preferred way of doing things over the other, and if so why? Thanks.

Copy link
Member

@dfawley dfawley left a comment

Choose a reason for hiding this comment

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

Lots of small things, many of which are subjective so feel free to disagree (ideally with rationale)! Also some of these things can just be cleaned up in a later PR.

"sync/atomic"
)

// BufferPool is a pool of buffers that can be shared, resulting in
Copy link
Member

Choose a reason for hiding this comment

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

Nit: that can be shared and reused? The "reuse" is the part that reduces allocations, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.

1 << 20, // 1MB
}

var defaultBufferPool = func() *atomic.Pointer[BufferPool] {
Copy link
Member

Choose a reason for hiding this comment

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

Since it's not exported I'm not too picky either way, but I agree that might be nicer.

// created with NewBufferPool that uses a set of default sizes optimized for
// expected workflows.
func DefaultBufferPool() BufferPool {
return *defaultBufferPool.Load()
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm, can we make this not an atomic in production code? I.e. in production the user needs to initialize it once at init time and we never use it until after init time. Tests need to change it dynamically, but generally, users shouldn't do that. (That doesn't have to block this PR.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.

// # Testing Only
//
// This function should ONLY be used for testing purposes.
func SetDefaultBufferPoolForTesting(pool BufferPool) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd like to work on moving this to internal instead to keep the API surface clean. Unless we think users need this for their own testing. As above, this PR is probably OK this way.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.

Comment on lines 69 to 71
// NewBufferPool returns a BufferPool implementation that uses multiple
// underlying pools of the given pool sizes.
func NewBufferPool(poolSizes ...int) BufferPool {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this should probably be NewTieredBufferPool since we may want to add other BufferPool implementations in the future that work differently. E.g. NewDynamicTierBufferPool or NewSimpleBufferPool or whatever.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.

// LazyMaterialize functions like Materialize except that it writes the data to a
// single Buffer pulled from the given BufferPool. As a special case, if the
// input BufferSlice only actually has one Buffer, this function has nothing to
// do and simply returns said Buffer, hence it being "lazy".
Copy link
Member

@dfawley dfawley Jul 25, 2024

Choose a reason for hiding this comment

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

This seems more like "smart" to me than "lazy"? I think "lazy" usually implies you don't do anything until you have to. (Feel free to disagree if you have any examples where I'm wrong.)

Also, you could maybe instead make this MaterializeBuffer or something and the other one MaterializeBytes to distinguish them? I feel like the behavior of this is really just "what it should be doing", given the function signature.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wasn't very convinced with the term "lazy" here as well and had made a comment on one of the previous PRs. See: #7356 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to Concatenate. I think it makes sense it's pretty descriptive that it's creating a new big buffer if needed but leaves room for the smart case to be smart.

Comment on lines 189 to 194
// the given BufferSlice. For example, in the context of a http.Handler, the
// following code can be used to copy the contents of a request into a
// BufferSlice:
//
// var out mem.BufferSlice
// n, err := io.Copy(mem.NewWriter(&out, pool), req.Body)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I prefer to leave code out of comments and use an Example test instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Added an example test for this.

mem/buffers.go Outdated
Comment on lines 58 to 60
if free != nil {
b.free = func() { free(data) }
}
Copy link
Member

Choose a reason for hiding this comment

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

Optional: Buffer contains data, immutably, so this could just do b := &Buffer{..., free: free} and invoke it with b.data when needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually important because the Split function changes b.data, so capturing this here avoids potential pitfalls down the line. It was initially set up that way but became too complicated to manage

mem/buffers.go Outdated
// to the returned Buffer are released.
//
// Note that the backing array of the given data is not copied.
func NewBuffer(data []byte, free func([]byte)) *Buffer {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: maybe name the parameter onFree since it's invoked when Free is called?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. Do you also want me to rename the field in Buffer?

Copy link
Member

Choose a reason for hiding this comment

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

Probably would be better, but that (or both, really) can be done as a cleanup later.

mem/buffers_test.go Outdated Show resolved Hide resolved
@dfawley dfawley removed their assignment Jul 25, 2024
@easwars easwars merged commit 887d908 into grpc:master Aug 1, 2024
13 checks passed
ijsong added a commit to kakao/varlog that referenced this pull request Oct 21, 2024
This PR removes the following gRPC buffer pool flags:

- `--grpc-server-recv-buffer-pool`
- `--grpc-client-recv-buffer-pool`

These flags are no longer necessary since gRPC introduced the `mem` package in
version [1.66.0](https://github.com/grpc/grpc-go/releases/tag/v1.66.0).

See:
- https://github.com/grpc/grpc-go/releases/tag/v1.66.0
- grpc/grpc-go#7432
- grpc/grpc-go#7356
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Feature New features or improvements in behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants