-
Notifications
You must be signed in to change notification settings - Fork 38.7k
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
reduce the number of allocations in the WatchServer during objects serialisation #108186
reduce the number of allocations in the WatchServer during objects serialisation #108186
Conversation
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/testing/types.go
Outdated
Show resolved
Hide resolved
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.
Just two quick comments - I will take a deeper look in the upcoming days, but I think we should address them before that.
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator.go
Outdated
Show resolved
Hide resolved
6e3c32a
to
a6bd72c
Compare
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator_test.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator_test.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go
Show resolved
Hide resolved
a6bd72c
to
bc76be4
Compare
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 great - I added some comments but those are relatively small.
The only bigger one is missing support for JSON (for CRD purpose).
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/allocator.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/encoder_with_allocator_test.go
Outdated
Show resolved
Hide resolved
b.Fatal(err) | ||
} | ||
} | ||
} |
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.
Can you paste the results of those benchmark into PR description?
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.
Ping
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.
heh, so my first idea was to create an HTML table to present the results. That turned out to be labor-intensive for me. So I wanted to use https://pkg.go.dev/golang.org/x/perf/cmd/benchstat but haven't found a good way to presents the results either.
Would be okay to just copy/paste the results from my terminal?
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.
Yes - I'm fine with anything as long as I can read it :)
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
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf_test.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf_test.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go
Show resolved
Hide resolved
855ce75
to
c97797a
Compare
Two things from my side. Some benchmarks for the |
…ialization. It allows us to allocate a single buffer for the entire watch session and release it when a watch connection is closed. Previously memory was allocated for every object serialization putting a lot of pressure on GC and consuming more memory than needed.
The new method is implemented by the protobuf serializer and helps to reduce memory footprint during object serialization.
c97797a
to
31ff8eb
Compare
ok, so I have addressed the recent comments, added some benchmarks and |
31ff8eb
to
73f3a7d
Compare
/kind feature |
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.
Just some minor nits - other than that LGTM
func (s *Serializer) doEncode(obj runtime.Object, w io.Writer) error { | ||
func (s *Serializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { | ||
if memAlloc == nil { | ||
return fmt.Errorf("a memory allocator must be provided") |
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 would prefer to logging an Error and falling back to SimpleAllocator in this case rather than failing the whole encoding.
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.
it cannot happen. I added it as a safety net, doEncode
is an internal method.
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.
memAlloc
is provided by Encode
and EncodeWithAllocator
methods.
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.
Yes - an one can call EncodeWithAllocator and pass nil memoryallocator
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.
In that case, a caller should use the Encode
method or provide a noop allocator.
I would prefer to make it explicit as it is less error-prone, i.e. some middle layer doesn't pass the provided allocated down.
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.
They should use Encode
but they made a bug. I think it's still better to mitigate the problem and not fail the request on the cost of worse performance.
func (s *RawSerializer) doEncode(obj runtime.Object, w io.Writer) error { | ||
func (s *RawSerializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { | ||
if memAlloc == nil { | ||
return fmt.Errorf("a memory allocator must be provided") |
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.
Same here
I was actually expecting this, but to close the loop - the benchmark results are amazing! |
The new method allows for providing a memory allocator for efficient memory usage during object serialization.
The primary use case for the allocator is to reduce cost of object serialization. Initially it will be used by the protobuf serializer. This approach puts less load on GC and leads to less fragmented memory in general.
…uring objects serialization
73f3a7d
to
9dd77ac
Compare
/lgtm Thanks! |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: p0lyn0mial, wojtek-t The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
/retest |
1 similar comment
/retest |
What type of PR is this?
/kind feature
What this PR does / why we need it:
The WatchServer is largely responsible for streaming data received from the storage layer. It turns out that sending a single event per consumer requires 4 memory allocations, visualized in the following image. Two of which deserve special attention, namely allocations 1 and 3 because they won't reuse memory and rely on the GC for cleanup. In other words, the more events we need to send, the more (temporary) memory will be used. In contrast, the other two allocations are already optimized they reuse memory instead of creating new buffers for every single event.
For better memory utilization, this PR changes the protobuf encoders to accept a memory allocator and changes the WatchServer to allocate a single buffer (combines 1, 3 and 4) for the entire watch session and pass it to encoders during objects serialization.
I am attaching the results from the benchmarks included in this PR that show an improvement.
The results for protobuf.Serializer
The results for protobuf.RawSerializer:
Which issue(s) this PR fixes:
kubernetes/enhancements#3157
Special notes for your reviewer:
you will find more info in kubernetes/enhancements#3142
Does this PR introduce a user-facing change?
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: