Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

don't allocate a wire.Header when parsing short header packets #2928

Closed
wants to merge 7 commits into from

Conversation

marten-seemann
Copy link
Member

This PR is part of the ongoing effort to reduce allocations in the fast path (after the handshake, when application data is exchanged). While we already use a sync.Pool for STREAM frames, and #2885 adds one for ACK frames, we still allocate a wire.ExtendedHeader for every incoming packet:
https://github.com/lucas-clemente/quic-go/blob/325bc1694dd84af5d96a1d4d9db7e011ec6f3664/internal/wire/extended_header.go#L20-L31
which contains an embedded wire.Header
https://github.com/lucas-clemente/quic-go/blob/325bc1694dd84af5d96a1d4d9db7e011ec6f3664/internal/wire/header.go#L48-L62
That's a pretty large data structure, especially considering that a QUIC short header packet only ever needs:

  • the key phase bit
  • the Destination Connection ID
  • the packet number

To avoid allocations, we can just define a function that returns (protocol.KeyPhaseBit, protocol.ConnectionID, protocol.PacketNumber) instead of a full wire.ExtendedHeader).
Unfortunately, making this change ended up in a major refactoring, as we have many functions that use a wire.ExtendedHeader as a function argument and then distinguish between long and short header packets in the function body. These functions had to be split up into one version for long and one for short header packets.

It will probably make sense to perform a similar refactoring for the code paths that send out packets.

@codecov
Copy link

codecov bot commented Dec 6, 2020

Codecov Report

Merging #2928 (a83b724) into master (325bc16) will increase coverage by 0.06%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2928      +/-   ##
==========================================
+ Coverage   86.22%   86.28%   +0.06%     
==========================================
  Files         132      132              
  Lines        9141     9183      +42     
==========================================
+ Hits         7881     7923      +42     
  Misses        910      910              
  Partials      350      350              
Impacted Files Coverage Δ
internal/wire/header.go 91.53% <100.00%> (+0.07%) ⬆️
logging/multiplex.go 97.56% <100.00%> (+0.13%) ⬆️
qlog/qlog.go 95.27% <100.00%> (+0.64%) ⬆️
session.go 79.54% <100.00%> (+0.14%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 325bc16...a20a606. Read the comment docs.

@marten-seemann marten-seemann force-pushed the no-header-alloc-for-short-headers branch 3 times, most recently from fb27af2 to e4e8025 Compare December 8, 2020 13:12
@marten-seemann marten-seemann force-pushed the no-header-alloc-for-short-headers branch from e4e8025 to ff69ca6 Compare December 8, 2020 13:24
@lucas-clemente
Copy link
Member

It's hard to judge whether the tradeoff between code complexity and performance is worth it here. Do you have some numbers on how much inefficiency the GC currently adds, and whether this improves throughput, latency, etc?

@@ -231,6 +231,32 @@ func (t *connectionTracer) SentPacket(hdr *wire.ExtendedHeader, packetSize proto
t.mutex.Unlock()
}

func (t *connectionTracer) SentShortHeaderPacket(destConnID logging.ConnectionID, pn logging.PacketNumber, kp logging.KeyPhaseBit, size logging.ByteCount, ack *logging.AckFrame, frames []logging.Frame) {
Copy link
Member

Choose a reason for hiding this comment

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

If you're constructing the Header{} struct anyway, why not do that when calling this function? That would make things more symmetrical with the LongHeader version, and may even allow you to call the Short version from the Long function?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, this is in the qlog package. I'm not worried about the additional allocation here. If you have qlog enabled, you can't expect maximum performance anyway.

@@ -272,6 +272,25 @@ func (t *connectionTracer) ReceivedPacket(hdr *wire.ExtendedHeader, packetSize p
t.mutex.Unlock()
}

func (t *connectionTracer) ReceivedShortHeaderPacket(destConnID logging.ConnectionID, pn logging.PacketNumber, kp logging.KeyPhaseBit, size protocol.ByteCount, frames []logging.Frame) {
Copy link
Member

Choose a reason for hiding this comment

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

As before: If you're constructing the Header{} struct anyway, why not do that when calling this function? That would make things more symmetrical with the LongHeader version, and may even allow you to call the Short version from the Long function?

@@ -10,6 +10,11 @@ import (
"github.com/lucas-clemente/quic-go/internal/utils"
)

// IsLongHeader says if a packet is a long header packet.
func IsLongHeader(firstByte byte) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Has?

@marten-seemann
Copy link
Member Author

marten-seemann commented Jan 26, 2023

We've recently resolved this as part of #3526.

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.

None yet

2 participants