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

Simplify client rpc execution #1993

Merged
merged 5 commits into from
Jul 29, 2024
Merged

Conversation

glbrntt
Copy link
Collaborator

@glbrntt glbrntt commented Jul 25, 2024

Motivation:

The existing code for executing RPCs on the client is layered, the one-shot, retry and hedging executors serialize the messages of the requests they're processing into bytes. These 'raw' requests are processed by the stream executor which is structured in such a way as requiring a task group of events it needs to handled. These events are handled in a run method (which is in turn another task for the caller to run). This requires quite a bit of machinery (and allocations) for it to work.

Modifications:

  • Push the serialization/deserialization of messages down into the stream executor. This lets a typed request be transformed directly to RPCRequestParts and for RPCResponseParts to be transformed directly to a typed response (rather than via an intermediary request/response typed to [UInt8]).
  • Remove the sendability requirement from the next function for client interceptors. This makes sense: interceptors should be straight-line code so shouldn't be shuffled off into a subtask. This unlocks the ability to remove the task group and event stream previously used by the stream executor as it can add child tasks to a provided task group (rather than needing a separate task group with an event stream).
  • Apply this change to the one-shot, hedging, and retry executors.

Result:

  • ~35% reduction in allocations for unary RPCs on the client

Motivation:

The existing code for executing RPCs on the client is layered, the
one-shot, retry and hedging executors serialize the messages of the
requests they're processing into bytes. These 'raw' requests are
processed by the stream executor which is structured in such a way as
requiring a task group of events it needs to handled. These events are
handled in a run method (which is in turn another task for the caller to
run). This requires quite a bit of machinery (and allocations) for it to
work.

Modifications:

- Push the serialization/deserialization of messages down into the
  stream executor. This lets a typed request be transformed directly to
  `RPCRequestPart`s and for `RPCResponsePart`s to be transformed
  directly to a typed response (rather than via an intermediary
  request/response typed to `[UInt8]`).
- Remove the sendability requirement from the `next` function for client
  interceptors. This makes sense: interceptors should be straight-line
  code so shouldn't be shuffled off into a subtask. This unlocks the
  ability to remove the task group and event stream previously used by
  the stream executor as it can add child tasks to a provided task group
  (rather than needing a separate task group with an event stream).
- Apply this change to the one-shot, hedging, and retry executors.

Result:

- ~35% reduction in allocations for unary RPCs on the client
next: @Sendable (
next: (
Copy link
Collaborator

Choose a reason for hiding this comment

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

you might want to adopt sending here instead. So that you can call an interceptor on a child task if one wants to

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I was thinking about that. I was going to go through this module separately and adopt sending since we're still using v5 language mode and I think we need it in a few places.

@glbrntt glbrntt requested a review from gjcairo July 26, 2024 09:20
@glbrntt glbrntt marked this pull request as ready for review July 26, 2024 09:20
@glbrntt glbrntt added the v2 A change for v2 label Jul 26, 2024
Comment on lines 28 to 29
@inlinable // would be private
static func execute<Input: Sendable, Output: Sendable>(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: do we want to rename to _execute?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah thanks. We actually don't want it to be _execute, it should be internal, it's the comment that's wrong.

@glbrntt glbrntt requested a review from gjcairo July 26, 2024 14:48
@glbrntt glbrntt merged commit 3f74983 into grpc:main Jul 29, 2024
14 of 17 checks passed
@glbrntt glbrntt deleted the simplify-rpc-execution branch July 29, 2024 10:32
clintonpi pushed a commit to clintonpi/grpc-swift that referenced this pull request Jul 29, 2024
* Simplify client rpc execution

Motivation:

The existing code for executing RPCs on the client is layered, the
one-shot, retry and hedging executors serialize the messages of the
requests they're processing into bytes. These 'raw' requests are
processed by the stream executor which is structured in such a way as
requiring a task group of events it needs to handled. These events are
handled in a run method (which is in turn another task for the caller to
run). This requires quite a bit of machinery (and allocations) for it to
work.

Modifications:

- Push the serialization/deserialization of messages down into the
  stream executor. This lets a typed request be transformed directly to
  `RPCRequestPart`s and for `RPCResponsePart`s to be transformed
  directly to a typed response (rather than via an intermediary
  request/response typed to `[UInt8]`).
- Remove the sendability requirement from the `next` function for client
  interceptors. This makes sense: interceptors should be straight-line
  code so shouldn't be shuffled off into a subtask. This unlocks the
  ability to remove the task group and event stream previously used by
  the stream executor as it can add child tasks to a provided task group
  (rather than needing a separate task group with an event stream).
- Apply this change to the one-shot, hedging, and retry executors.

Result:

- ~35% reduction in allocations for unary RPCs on the client

* use task group void

* fix comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v2 A change for v2
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants