Encode response headers against the QPACK static table - #1
Merged
Conversation
The encoder only ever used the static table for :status. Every other field was written as Literal With Literal Name, spelling out a name the table already holds - so a response carrying content-type, date and cache-control put all three names on the wire in full, on every response. Three tiers now, cheapest first: an entry matching name and value is one byte, a known name is an index plus the literal value, and only an unknown name is spelled out. content-type: text/plain is entry 53, so it goes from 25 bytes to 1, and the header block for a small response drops from 28 bytes to 4. Names match case-insensitively. Callers hold HTTP's conventional capitalisation, and resolving it here means the name is never written, so there is nothing to lowercase and nothing to allocate for it. Values match exactly, since a field value is case-sensitive. This is worth more in practice than the dynamic table: it works against every client, whereas the dynamic table stays inert unless the peer advertised capacity, which only browsers do. Throughput is unchanged on a small-response benchmark (269k vs 268k req/s), which is expected - that workload is not bandwidth-bound. The gain is bytes per response, and it compounds with anything that packs more responses per datagram.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The encoder only ever consulted the static table for
:status. Every other field went out as Literal With Literal Name, spelling out a name the table already holds — so a response carryingcontent-type,dateandcache-controlput all three names on the wire in full, on every response.Response encoding is now three tiers, cheapest first:
0xC0)0x50)0x20)content-type: text/plainis static entry 53, so it goes from 25 bytes to 1, and the header block for a small response drops from 28 bytes to 4.Names match case-insensitively. Callers hold HTTP's conventional capitalisation (
Content-Type), and resolving it here means the name is never written at all — so there is nothing to lowercase and nothing to allocate for it. Values match exactly, since a field value is case-sensitive.Why this over the dynamic table
It works against every client. The dynamic table stays inert unless the peer advertised capacity for one, and measuring across clients, only Chrome does — curl, h3x and .NET's client all advertise 0.
Measurements
:status: 200+content-type: text/plain: 28 → 4 bytesTests
129 pass (17 new). Encoding is verified by decoding what it produces, so an off-by-one index fails here rather than on a peer. Covered: exact matches across several entries, known-name-unknown-value, capitalised names, unknown names still written out, and case-sensitive value matching (
TEXT/PLAINmust not resolve to entry 53).Version
Bumped to 0.12.0 — minor rather than patch, since it changes what goes on the wire.