-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Conversation
The committers listed above are authorized under a signed CLA. |
Codecov ReportAttention: Patch coverage is
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
|
cc: @printchard |
mem
packagemem
package to facilitate memory reuse
mem/buffer_pool.go
Outdated
1 << 20, // 1MB | ||
} | ||
|
||
var defaultBufferPool = func() *atomic.Pointer[BufferPool] { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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.
@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. |
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 |
- add more tests - use got before want in tests - cleanup some docstrings - wrap comments at 80-cols
- and add tests for the BufferSlice
I've pushed a few more commits with more tests and in the process of writing them, I found a bug in the |
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. |
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. |
There was a problem hiding this 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.
mem/buffer_pool.go
Outdated
"sync/atomic" | ||
) | ||
|
||
// BufferPool is a pool of buffers that can be shared, resulting in |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
mem/buffer_pool.go
Outdated
1 << 20, // 1MB | ||
} | ||
|
||
var defaultBufferPool = func() *atomic.Pointer[BufferPool] { |
There was a problem hiding this comment.
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.
mem/buffer_pool.go
Outdated
// created with NewBufferPool that uses a set of default sizes optimized for | ||
// expected workflows. | ||
func DefaultBufferPool() BufferPool { | ||
return *defaultBufferPool.Load() |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
mem/buffer_pool.go
Outdated
// # Testing Only | ||
// | ||
// This function should ONLY be used for testing purposes. | ||
func SetDefaultBufferPoolForTesting(pool BufferPool) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
mem/buffer_pool.go
Outdated
// NewBufferPool returns a BufferPool implementation that uses multiple | ||
// underlying pools of the given pool sizes. | ||
func NewBufferPool(poolSizes ...int) BufferPool { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
mem/buffer_slice.go
Outdated
// 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". |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
mem/buffer_slice.go
Outdated
// 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
if free != nil { | ||
b.free = func() { free(data) } | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
- fix grammar in test error string - move SetDefaultBufferPoolForTesting to the `internal` package
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
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: