Skip to content

Optimize key formatter #597

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

moberegger
Copy link

@moberegger moberegger commented May 30, 2025

Makes a few optimizations to the Jbuilder::KeyFormatter integration:

  • Uses *args and **kwargs to handle the provided format symbols. This saves on having to compute a list of formatters from the provided options, and saves on some memory allocations for the empty [] arrays that represented "no parameters". While this is definitely a micro-optimization (ie. it would only be a hot code path of a template itself uses json.key_format!) it does make things easier to read.
  • Uses .each over .inject when computing cache keys, which is slightly faster.
  • Saves on some memory allocation when initializing either Jbuilder or JbuilderTemplate with no options, which is what the template handler does (json||=JbuilderTemplate.new(self)). No need to allocation an empty Hash for this.
  • Optimizes Jbuilder initialization by using key arguments instead of fetch when grabbing values from options
  • Saves on a memory allocation for both key_format! and self.key_format by simply passing args through to KeyFormatter.new. This is a micro-optimization, but can be a hotter code path if a template uses json.key_format!.

Now the big change here is that the key formatter cache is now no longer clobbered between template renders. What was originally happening was that when you configured a global key formatter with something like Jbuilder.key_format = camelize: :lower, it would be .cloned whenever a new Jbuilder was initialized, which happens before each template render. When it was cloned, the cache would be wiped it when KeyFormatter#initialize_copy ran. This meant that each template render - and thus each API request - would start with a fresh cache. This meant that you would have to re-pay the cost to format the keys all over again.

I'm not sure why it was done this way. When configuring a global key formatter for Jbuilder, I would expect that to be used across requests so that the cost to generate the keys could amortize as the service runs. This behaviour was originally added twelve years ago, specifically in this commit, but it's not clear to me what the intent or motivation was to do that.

The other difference is that a Mutex has been added to the cache lookup to keep it thread safe. This allows us to have a single shared cache per KeyFormatter. The previous implementation was thread safe because each render would have its own instance of the cache (and perhaps that's why the cache would be a fresh Hash whenever KeyFormatter was cloned), and the addition of the Mutex respects that behaviour.

@moberegger moberegger force-pushed the moberegger/optimize_key_formatter branch from 434c7aa to 2f3f902 Compare June 3, 2025 15:02
@moberegger moberegger force-pushed the moberegger/optimize_key_formatter branch from 2f3f902 to be609e7 Compare June 3, 2025 15:04
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.

1 participant