Skip to content

Simple optimizations for Deserializer performance - #67

Merged
tfpauly merged 2 commits into
mainfrom
tfp/optimize-deserialize
Aug 5, 2026
Merged

Simple optimizations for Deserializer performance#67
tfpauly merged 2 commits into
mainfrom
tfp/optimize-deserialize

Conversation

@tfpauly

@tfpauly tfpauly commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator
  • Avoid allocating scratchSpace up front
  • Inline more functions
  • Use an "empty" factory instead of a "single" span factory for the simple Frame case

@agnosticdev agnosticdev left a comment

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 for this PR. I can appreciate that this keeps the Deserializer API altogether in one piece but I feel we still need more from the performance here.
Two things are affecting the performance:

  1. Generating the bytes property each time we want to Deserialize.
  2. Running this all in a closure eats up some CPU too.

One excellent example that comes to mind here is parsing a short header packet on a busy connection. On a busy connection there can be hundreds of thousands of packets and when parsing a short header packet we would need to parse the first octet on the packet to determine the packet type (short of long header) and then parse the connection ID. So that computes a bytes property for reading the octet and then again for reading the connection ID for each short header packet.

Approving to move the needle here but we cannot continue on with the CPU cost of this computed property. We need to come up with a solution here.

guard isValid else { return nil }
switch buffer {
case .bytes:
return _bytes.span.extracting(startOffset..<(effectiveBufferLength - endOffset)).bytes

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.

In my local benchmark for Deserializing 1 frame 10,000,000 times this generating this computed property accounts for 17% of the CPU used. In an ideal world we would not have to do this at all.
We can claw a back bit more CPU back from here if we use:

 return _bytes.span.extracting(unchecked: startOffset..<(effectiveBufferLength - endOffset)).bytes

@@ -761,30 +789,36 @@ public struct Deserializer<Factory: DeserializerSpanFactory & ~Copyable & ~Escap
}

public static func deserialize(

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.

We pay a tax here having this all done through a closure, we can claw back a little if inline this function.

@tfpauly
tfpauly merged commit cb3e557 into main Aug 5, 2026
23 checks passed
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.

2 participants