Replaces the parser hot path's per-request String allocation
for the 9 RFC 7231 method names (GET, POST, PUT, PATCH, DELETE,
HEAD, OPTIONS, CONNECT, TRACE) and a small set of frequently-seen
header values (text/html / text/plain / application/ json / application/octet-stream / gzip / br /
deflate / identity / keep-alive / close,
HTTP/1.0 / HTTP/1.1) with shared StaticString
constants.
Background
flare.http.server._parse_http_request_bytes allocates the
method String twice per request today (the
String(String(unsafe_from_utf8=...)) double-wrap on line ~803
of server.mojo). Under TFB plaintext at 220K req/s + 4
workers, that's ~1.7M allocations / s for the method string
alone, with ~99 % of them being one of "GET" or "POST". Folding
those into a shared StaticString constant drops the allocation
to a single String.copy() of a 3-4 byte SSO buffer (which is
free on every Mojo target where short-string-optimisation kicks
in below the inline-String threshold).
The savings on the value-side are smaller per-call but
compounding: every Compress[Inner] middleware response sets
Content-Encoding: gzip (or br); every keep-alive response
sets Connection: keep-alive; every JSON response sets
Content-Type: application/json. Caching those as
StaticString lets future hot-path serializers compare against
the interned shape without an allocation.
What this module provides
MethodIntern — namespace struct holding the 9 method
StaticString constants. Mirrors the
flare.http.request.Method namespace but with
StaticString-typed fields so the constants can be passed
into intern_method_bytes lookups without a round-trip
through String.
ValueIntern — namespace struct holding the common
Content-Type / Content-Encoding / Connection / version
StaticString constants.
intern_method_bytes(Span[UInt8, _]) -> Optional[String] —
returns Optional[String] built from the interned
StaticString when slice exactly equals one of the
9 method names, otherwise None. Caller that gets None
back falls back to its existing String-construction path.
intern_common_value(Span[UInt8, _]) -> Optional[String]
— same shape for the value table.
- Both lookups are O(1) per slice — first dispatched by length,
then a tiny per-length switch (most lengths have ≤ 2
candidates).
Wiring into the parser is a follow-up; this module provides
the primitive + tests + re-exports so the wiring change is
mechanical.
Implementation notes
Lookup is length-first dispatch + byte compare. GET /
PUT (length 3) and POST / HEAD (length 4) cover
~95 % of real-world traffic; the longer methods (PATCH / DELETE
/ OPTIONS / CONNECT / TRACE) are fall-through. The branch
predictor learns "length=3 → GET" within the first few requests
on any given connection. No hash table, no Dict, no
multiplication.
The value table is similarly length-dispatched. Lengths uniquely
identify most candidates (br is the only 2-char encoding,
gzip the only 4-char one; HTTP/1.0 and HTTP/1.1
share length 8 and are differentiated by the last byte; etc.).
v0.10 wiring result -- MEASURED, NOT ADOPTED for header values.
intern_common_value in the header-parse loop showed no consistent
effect (-0.8 %, +5.2 %, -13.1 % across three paired 30 s runs; median
worse). The reason is that _string_from_static returns
String(s), which for a value short enough to be worth interning is
covered by short-string optimisation -- so both paths avoid the heap
and the lookup is pure added work.
intern_method_bytes stays wired on the request line: the method set
is tiny and the dispatch is a single length compare.