Skip to content

nghttp2: a request allocates about 650 B of parser objects that could be reused per connection #489

Closed

Description

@MDA2AV

Each buffered request in ioxide.nghttp2 allocates about 655 B in objects made per request. It is the same set that #486 removes from ioxide.http2 for #485. Async machinery is small here: about 7 B per request, one FlushEgressAsync box per pass.

Measured on main eb5f515 (paths under src/protocols/ioxide.nghttp2/ unless named):

  • App: Playground/Http2/Nghttp2Buffered, path /.
  • Setup: 1 reactor pinned to one core, and h2load -c 32 -m 16 -t 1 pinned to another physical core: 1.16M req/s.
  • Method: a 10 s dotnet-trace --profile gc-verbose window, allocations grouped by type and first ioxide frame.
Allocated B per request Where
Request header array KeyValuePair<ReadOnlyMemory<byte>, ReadOnlyMemory<byte>>[8] 279 First Headers.Add in PendingRequest.Freeze (Connection/Nghttp2Connection.cs:247)
Nghttp2Request 119 PendingRequest.Freeze (Connection/Nghttp2Connection.cs:234)
PendingRequest 96 CallbackBeginHeaders (Connection/Nghttp2Connection.Callbacks.cs:50)
Field-range array (int, int, int, int)[] 88 AddField (Connection/Nghttp2Connection.cs:214), from CallbackHeader
Request KeyValueList 32 Nghttp2Request.Headers (Http/Nghttp2Request.cs:29)
Field-range List 32 PendingRequest._fields (Connection/Nghttp2Connection.cs:164)
PendingRequest[] 9 _readyThisPass.ToArray() (Connection/Nghttp2Connection.RunBuffered.cs:138), one per pass
FlushEgressAsync box 7 One per pass
Nghttp2Response and its KeyValueList 48 + 33 The handler's own new Nghttp2Response (app code)

The streamed sample (Playground/Http2/Nghttp2Response, 8 chunks of 1 KiB):

Possible fix: the one #486 applies to ioxide.http2.

  • Return a retired PendingRequest to its connection with its Nghttp2Request, header KeyValueList and _fields, and reuse them for the next stream.
    • It is retired at the pending.Dispose() calls in Connection/Nghttp2Connection.RunBuffered.cs, and in ServeStreamedAsync (Connection/Nghttp2Connection.Streamed.cs:95).
    • KeyValueList.Clear already keeps its array for this.
  • The request object is then reused once its handler is done, as in http2: a streamed flush that waits for the write pump allocates nothing, and a connection reuses its request objects #486. It was already only valid during the handler.
  • Swap two lists instead of _readyThisPass.ToArray().

In ioxide.http2, the same change took a request from 793 B to 89 B, and server CPU per request down by 17%.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions