Skip to content

Commit

Permalink
Provide a CoW box for large HTTPFrame.FramePayload cases. (apple#110)
Browse files Browse the repository at this point in the history
Motivation:

Anything that passes through a NIO pipeline in high volume that is not special-cased by NIOAny
needs to have careful attention paid to its size. This is because if it's more than 3 64-bit words
wide (24 bytes), it will need to be heap-allocated to be stuffed into the Any case of the NIOAny.

Sadly, HTTP2Frame was 44 bytes wide: altogether too wide for the pipeline. This was forcing heap
allocations each time a frame was sent or received in the pipeline, which is not so good.

To avoid this problem we normally turn our data structures into a CoW box. We want to do that
here as well, but we need to be a bit careful. Many parts of the code access the `streamID` field
frequently, and placing the stream ID into the CoW box will slow those accesses down by requiring a
memory load from a non-stack location. Additionally, code frequently checks the payload discriminator
byte, which we would like out of the box as well if at all possible.

It turns out that we have a neat tool at our disposal to achieve this: `indirect case`. This allows
us to ask the Swift compiler to box a structure *when stored in an enum case*. The effect is to shrink
the size of the enumeration when passed around as an enum, but nonetheless allow the contained objects
to be identical in form and API to when they were unboxed. It's very helpful here.

This shrinks the size of the HTTP2Frame data structure from 44 bytes to 18 bytes, dropping the size of
the payload enumeration to 10 bytes and leading to a 10% perf gain in some microbenchmarks.

Modifications:

- Placed large HTTP2Frame.FramePayload cases into a box.

Result:

Performance improvements!
  • Loading branch information
Lukasa authored May 2, 2019
1 parent 8752535 commit 4097c3a
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 92 deletions.
23 changes: 11 additions & 12 deletions Sources/NIOHTTP2/HTTP2Frame.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import NIOHPACK

/// A representation of a single HTTP/2 frame.
public struct HTTP2Frame {
/// The payload of this HTTP/2 frame.
public var payload: FramePayload


/// The frame stream ID as a 32-bit integer.
public var streamID: HTTP2StreamID


/// The payload of this HTTP/2 frame.
public var payload: FramePayload

/// Stream priority data, used in PRIORITY frames and optionally in HEADERS frames.
public struct StreamPriorityData: Equatable, Hashable {
public var exclusive: Bool
Expand All @@ -36,7 +37,7 @@ public struct HTTP2Frame {
/// A DATA frame, containing raw bytes.
///
/// See [RFC 7540 § 6.1](https://httpwg.org/specs/rfc7540.html#rfc.section.6.1).
case data(FramePayload.Data)
indirect case data(FramePayload.Data)

/// A HEADERS frame, containing all headers or trailers associated with a request
/// or response.
Expand All @@ -45,7 +46,7 @@ public struct HTTP2Frame {
/// frames into a single `FramePayload.headers` instance.
///
/// See [RFC 7540 § 6.2](https://httpwg.org/specs/rfc7540.html#rfc.section.6.2).
case headers(Headers)
indirect case headers(Headers)

/// A PRIORITY frame, used to change priority and dependency ordering among
/// streams.
Expand Down Expand Up @@ -78,7 +79,7 @@ public struct HTTP2Frame {
///
/// For more information on server push in HTTP/2, see
/// [RFC 7540 § 8.2](https://httpwg.org/specs/rfc7540.html#rfc.section.8.2).
case pushPromise(PushPromise)
indirect case pushPromise(PushPromise)

/// A PING frame, used to measure round-trip time between endpoints.
///
Expand All @@ -91,7 +92,7 @@ public struct HTTP2Frame {
/// some additional diagnostic data.
///
/// See [RFC 7540 § 6.8](https://httpwg.org/specs/rfc7540.html#rfc.section.6.8).
case goAway(lastStreamID: HTTP2StreamID, errorCode: HTTP2ErrorCode, opaqueData: ByteBuffer?)
indirect case goAway(lastStreamID: HTTP2StreamID, errorCode: HTTP2ErrorCode, opaqueData: ByteBuffer?)

/// A WINDOW_UPDATE frame. This is used to implement flow control of DATA frames,
/// allowing peers to advertise and update the amount of data they are prepared to
Expand All @@ -106,7 +107,7 @@ public struct HTTP2Frame {
/// the locations at which they may be addressed.
///
/// See [RFC 7838 § 4](https://tools.ietf.org/html/rfc7838#section-4).
case alternativeService(origin: String?, field: ByteBuffer?)
indirect case alternativeService(origin: String?, field: ByteBuffer?)

/// An ORIGIN frame. This allows servers which allow access to multiple origins
/// via the same socket connection to identify which origins may be accessed in
Expand Down Expand Up @@ -243,12 +244,10 @@ public struct HTTP2Frame {
}
}
}
}

extension HTTP2Frame {
/// Constructs a frame header for a given stream ID. All flags are unset.
public init(streamID: HTTP2StreamID, payload: HTTP2Frame.FramePayload) {
self.streamID = streamID
self.payload = payload
self.streamID = streamID
}
}
Loading

0 comments on commit 4097c3a

Please sign in to comment.