Skip to content

Look content types up in a plain Hash instead of an indifferent one - #2883

Open
ericproulx wants to merge 1 commit into
masterfrom
perf/content-type-lookup
Open

Look content types up in a plain Hash instead of an indifferent one#2883
ericproulx wants to merge 1 commit into
masterfrom
perf/content-type-lookup

Conversation

@ericproulx

Copy link
Copy Markdown
Contributor

PrecomputedContentTypes#content_type_for answers what media type a format maps to, and backed that with a HashWithIndifferentAccess:

def content_type_for(format)
  content_types_indifferent_access[format]
end

Every read ran convert_key before the lookup, because the callers genuinely disagree on spelling — a format negotiated from a path extension, a ?format= or an Accept header arrives as a String, while the API's declared format and the value stashed in api.format are Symbols.

Both spellings can live in the same Hash instead. It is built once per middleware and never written again, so that conversion is work with only one possible answer.

Measurements

Same-process A/B on the lookup:

HWIA plain Hash
[:json] 42.3 ns 25.5 ns 1.66x
['json'] 38.8 ns 33.7 ns 1.15x

content_type_for runs twice per request — once to negotiate the format, once to set the response content type — and three times when the path carries a format extension (measured by instrumenting the method).

That is about 34 ns per request, roughly 0.6% of a minimal one. End to end this is below what the harness can resolve — repeated runs straddle zero — so no end-to-end figure is claimed. Allocation counts are unchanged: convert_key used Symbol#name, which allocates nothing.

Equivalence

Keys arrive as Symbols — DSL::RequestResponse#content_type calls key.to_sym, and ContentTypes::DEFAULTS are Symbols — but the key is stored as it came as well, so a middleware constructed directly with String keys still answers to either spelling, exactly as the indifferent hash did.

Checked against HashWithIndifferentAccess over the default content types plus unknown, nil and empty keys (16 probes, identical answers), and over a string-keyed hash looked up both ways:

equivalent on 16 probe keys (incl. unknown, nil, empty)
string-keyed: symbol lookup true, string lookup true

Suite green (2652), RuboCop clean.

🤖 Generated with Claude Code

`PrecomputedContentTypes#content_type_for` answers what media type a format
maps to, and backed that with a `HashWithIndifferentAccess`:

    def content_type_for(format)
      content_types_indifferent_access[format]
    end

Every read therefore ran `convert_key` before the lookup, because the callers
disagree on spelling: the format negotiated from a path extension, a `?format=`
or an `Accept` header arrives as a String, while the API's declared format and
the value stashed in `api.format` are Symbols.

Both spellings can be present in the same Hash instead. It is built once per
middleware and never written again, so the conversion is work that only ever
had one answer.

    HWIA[:json]     42.3 ns  ->  Hash[:json]     25.5 ns   1.66x
    HWIA['json']    38.8 ns  ->  Hash['json']    33.7 ns   1.15x

`content_type_for` is called twice on a request — once to negotiate the format,
once to set the response content type — and three times when the path carries a
format extension. That is about 34 ns per request, roughly 0.6% of a minimal
one. End to end it sits below what the version_throughput harness can resolve,
so no end-to-end figure is claimed here. Allocation counts are unchanged:
`convert_key` used `Symbol#name`, which allocates nothing.

Keys arrive as Symbols — the `content_type` DSL calls `key.to_sym` and the
defaults are Symbols — but the key is also stored as it came, so a middleware
constructed directly with String keys still answers to either spelling, as the
indifferent hash did. Checked against `HashWithIndifferentAccess` over the
default content types plus unknown, nil and empty keys, and over a string-keyed
hash looked up both ways: same answer every time.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the perf/content-type-lookup branch from 3739314 to 4b95a6b Compare September 1, 2026 21:13
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@dblock

dblock commented Sep 2, 2026

Copy link
Copy Markdown
Member

This feels like an over-optimization but ok.

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