Skip to content

Reuse the received frame deque - #64

Merged
tfpauly merged 2 commits into
apple:mainfrom
glbrntt:packet-queue
Aug 4, 2026
Merged

Reuse the received frame deque#64
tfpauly merged 2 commits into
apple:mainfrom
glbrntt:packet-queue

Conversation

@glbrntt

@glbrntt glbrntt commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Motivation:

Each Packet holds its own deque of parsed frames. The parser also reserved enough capacity for one frame, so every packet allocs at least once and then reallocs if packets contain subsequent frames.

The frames only live between parsing and calling handleInboundPacket, so the storage doesn't need to be tied to a Packet.

Modifications:

  • Move the deque of recevied frames from Packet to PacketParser and reserve its capacity once per connection.
  • Collapse the two copies of the frame-finalizing drain loop into PacketParser.cleanupReceivedFrames().
  • Shrink the deque back down once drained if a packet grew it beyond 128 frames (by default), so that a peer sending a packet of single byte frames can't pin that storage for the lifetime of the connection.

Result:

Fewer allocations

Motivation:

Each Packet holds its own deque of parsed frames. The parser also
reserved enough capacity for one frame, so every packet allocs at least
once and then reallocs if packets contain subsequent frames.

The frames only live between parsing and calling handleInboundPacket, so
the storage doesn't need to be tied to a Packet.

Modifications:

- Move the deque of recevied frames from Packet to PacketParser and
  reserve its capacity once per connection.
- Collapse the two copies of the frame-finalizing drain loop into
  PacketParser.cleanupReceivedFrames().
- Shrink the deque back down once drained if a packet grew it beyond 128
  frames (by default), so that a peer sending a packet of single byte
  frames can't pin that storage for the lifetime of the connection.

Result:

Fewer allocations
@glbrntt

glbrntt commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

This reduced the runtime of QUICTransfer by ~2% and reduced the number of allocations by nearly 5%.

originalLength: originalLength
)
}
packet.framesReceived.reserveCapacity(1)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you! This will show a pretty nice perf impact here. Last time I measure should be around 133 megacycles.

var framesReceived = NetworkUniqueDeque<QUICFrame>()

// Initial capacity for `framesReceived`.
private static var framesReceivedCapacity: Int { 16 }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is probably too high because most QUIC packets have only a handful of frames. What about starting with 4 ?

Comment thread Sources/SwiftNetwork/QUIC/PacketParser.swift Outdated

func testLayoutPacket() {
let packetSize = 188
let packetSize = 156

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NICE! Thank you!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

FWIW, and I didn't look at this in any depth, but it looked like shorthandFrames may be a candidate for a similar optimization here.

@tfpauly
tfpauly merged commit 15cb148 into apple:main Aug 4, 2026
23 checks passed
@glbrntt
glbrntt deleted the packet-queue branch August 4, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants