Skip to content

Conversation

@arjan-bal
Copy link
Contributor

@arjan-bal arjan-bal commented Oct 9, 2025

This PR improves performance by eliminating heap allocations when multiple stats handlers are configured.

Previously, iterating through a list of handlers caused one heap allocation per handler for each RPC. This change introduces a Handler that combines multiple Handlers and implements the Handler interface. The combined handler delegates calls to the handlers it contains.

This approach allows gRPC clients and servers to operate as if there were only a single Handler registered, simplifying the internal logic and removing the per-RPC allocation overhead. To avoid any performance impact when stats are disabled, the combined Handler is only created when at least one handler is registered.

Tested

Since existing benchmarks don't register stats handler, I modified the benchmark to add 2 stats handlers each on the server and client (36ba616).

# test command
go run benchmark/benchmain/main.go -benchtime=60s -workloads=unary \
   -compression=off -maxConcurrentCalls=200 -trace=off \
   -reqSizeBytes=100 -respSizeBytes=100 -networkMode=Local -resultFile="${RUN_NAME}"

# results
go run benchmark/benchresult/main.go unary-before unary-after
               Title       Before        After Percentage
            TotalOps      7336128      7638892     4.13%
             SendOps            0            0      NaN%
             RecvOps            0            0      NaN%
            Bytes/op     12382.19     11467.03    -7.39%
           Allocs/op       173.93       165.91    -4.60%
             ReqT/op  97815040.00 101851893.33     4.13%
            RespT/op  97815040.00 101851893.33     4.13%
            50th-Lat   1.463345ms   1.403011ms    -4.12%
            90th-Lat   2.557136ms    2.46828ms    -3.47%
            99th-Lat   3.073264ms   3.080081ms     0.22%
             Avg-Lat   1.634153ms   1.569391ms    -3.96%
           GoVersion     go1.24.7     go1.24.7
         GrpcVersion   1.77.0-dev   1.77.0-dev

RELEASE NOTES:

  • stats: Reduce heap allocations when multiple stats Handlers are registered.

@arjan-bal arjan-bal changed the title stasts: Re-use objects while calling multiple Handlers stats: Re-use objects while calling multiple Handlers Oct 9, 2025
@arjan-bal arjan-bal added Type: Performance Performance improvements (CPU, network, memory, etc) Area: Observability Includes Stats, Tracing, Channelz, Healthz, Binlog, Reflection, Admin, GCP Observability labels Oct 9, 2025
@arjan-bal arjan-bal modified the milestone: 1.77 Release Oct 9, 2025
@codecov
Copy link

codecov bot commented Oct 9, 2025

Codecov Report

❌ Patch coverage is 98.55072% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.00%. Comparing base (0e9ac5d) to head (197815c).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
internal/transport/http2_client.go 90.47% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8639      +/-   ##
==========================================
+ Coverage   81.28%   82.00%   +0.72%     
==========================================
  Files         415      417       +2     
  Lines       40888    40793      -95     
==========================================
+ Hits        33234    33452     +218     
+ Misses       6162     5981     -181     
+ Partials     1492     1360     -132     
Files with missing lines Coverage Δ
clientconn.go 90.08% <100.00%> (+0.27%) ⬆️
internal/stats/stats.go 100.00% <100.00%> (ø)
internal/transport/handler_server.go 90.85% <100.00%> (+<0.01%) ⬆️
internal/transport/http2_server.go 90.57% <100.00%> (-0.57%) ⬇️
internal/transport/transport.go 90.72% <ø> (+6.85%) ⬆️
server.go 81.98% <100.00%> (-0.10%) ⬇️
stream.go 81.60% <100.00%> (+0.03%) ⬆️
internal/transport/http2_client.go 92.36% <90.47%> (+8.22%) ⬆️

... and 41 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@arjan-bal arjan-bal force-pushed the stats-handlers-reuse-event-objects branch 2 times, most recently from a36c3d1 to 88fb8c2 Compare October 9, 2025 16:37
@arjan-bal arjan-bal force-pushed the stats-handlers-reuse-event-objects branch from 88fb8c2 to 38cc04a Compare October 9, 2025 16:39
@arjan-bal arjan-bal requested a review from easwars October 9, 2025 16:49
@arjan-bal arjan-bal requested a review from dfawley October 9, 2025 18:21
@easwars
Copy link
Contributor

easwars commented Oct 10, 2025

Do you have any benchmark numbers for this change?

Copy link
Contributor

@easwars easwars left a comment

Choose a reason for hiding this comment

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

Also, I was wondering what if you exported the combinedHandler and used it as a concrete type instead of as stats.Handler? The grpc server and the channel could use this concrete type, and any other internal type could as well. For external facing APIs that are expecting a stats.Handler, it should still continue to work if the caller has the concrete type. But again, I don't know if this will have any bearing on the performance. So, if this is going to increase the scope of the PR, please push back. Thanks.

@easwars easwars assigned arjan-bal and unassigned easwars and dfawley Oct 10, 2025
@arjan-bal
Copy link
Contributor Author

Do you have any benchmark numbers for this change?

I had to temporarily modify the benchmarks to register stats handlers on the client and server. I've added the results in the PR description.

@arjan-bal
Copy link
Contributor Author

Also, I was wondering what if you exported the combinedHandler and used it as a concrete type instead of as stats.Handler? The grpc server and the channel could use this concrete type, and any other internal type could as well. For external facing APIs that are expecting a stats.Handler, it should still continue to work if the caller has the concrete type. But again, I don't know if this will have any bearing on the performance. So, if this is going to increase the scope of the PR, please push back. Thanks.

I considered exporting combinedHandler and returning it as a concrete type but ultimately felt that returning the stats.Handler interface was a cleaner design. My main reasons are:

  1. Reduced Coupling: It prevents the gRPC server and channel from needing to know about the internal combinedHandler type, which feels like a more robust, long-term design.
  2. Encapsulation: It avoids exposing the implementation details of how multiple handlers are managed.
  3. Optimization: By returning the interface, we can add a small optimization: if only one stats handler is registered, we can return it directly without the overhead of wrapping it in a combinedHandler at all.

@arjan-bal arjan-bal assigned easwars and unassigned arjan-bal Oct 16, 2025
@easwars easwars assigned arjan-bal and unassigned easwars Oct 16, 2025
@arjan-bal arjan-bal merged commit ab1ca08 into grpc:master Oct 17, 2025
15 checks passed
@arjan-bal arjan-bal deleted the stats-handlers-reuse-event-objects branch October 17, 2025 06:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Observability Includes Stats, Tracing, Channelz, Healthz, Binlog, Reflection, Admin, GCP Observability Type: Performance Performance improvements (CPU, network, memory, etc)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants