Skip to content

Skip cache digest scans that can't find anything - #2775

Merged
joelhawksley merged 1 commit into
ViewComponent:mainfrom
erikaxel:cache-digest-skip-scans
Nov 4, 2026
Merged

joelhawksley merged 1 commit into
ViewComponent:mainfrom
erikaxel:cache-digest-skip-scans

Conversation

@erikaxel

@erikaxel erikaxel commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

What are you trying to accomplish?

A follow-up to #2769, #2771 and #2772. On 4.22.0, about 14% of a cold cache digest in our app (1,500 components, about 16 shared Ruby files each) goes to two scans that find nothing:

  • Resolver#explicit_dependencies runs EXPLICIT_DEPENDENCY over every Ruby source and template a component depends on. Almost none declare a # Template Dependency:, but the pattern starts with #\s*, so it's slow on Ruby sources, where # is common. This was 10.8% of a wall-clock profile of cold digests.
  • DependencyTracking#find_dependencies runs explicit_component_dependencies over every synthetic template before returning its dependencies. The resolver already translated class names to paths when it wrote those declarations, so there is never anything to swap. This was 2.9%.

Run alternately three times each against main (4.22.0) on the same machine:

4.22.0 This branch
precompute! over every component at boot 4.14–4.32 s 3.28–3.40 s
Cold digests of 152 components, DetailsKey.clear before each 27.0–27.7 s 23.2–23.8 s

All 1,500 digests computed at boot are identical to 4.22.0, and so are the 152 cold digests.

What approach did you choose and why?

  • The literal check. Resolver#explicit_dependencies checks source.include?("Template Dependency:") before scanning. The pattern can only match where that literal appears, so the result is unchanged.
  • The synthetic-template return. DependencyTracking#find_dependencies now returns a synthetic template's dependencies before the class-name translation rather than after it. Every declaration in a synthetic source comes from Resolver#dependencies, which has already translated the class names it could resolve. A name it couldn't resolve wouldn't resolve in a second pass either.

Anything you want to highlight for special attention from reviewers?

  • Tests. No new tests: neither change alters a result, and the existing tests for explicit dependencies, both in component sources and in templates, cover the paths involved. Tests, standardrb and Audition pass locally on Rails 8.1 and Ruby 4.0.
  • Remaining cost. After this, it's mostly Prism.parse and each_node over each distinct source once and GC.

`Resolver#explicit_dependencies` scanned every Ruby source and template for
`# Template Dependency:` with a regular expression, although almost none
declare one; check for the literal first. `DependencyTracking` translated
declared class names in synthetic templates, whose declarations the resolver
already wrote as paths; return their dependencies before that pass.
@erikaxel

erikaxel commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

PS: Thanks for the quick merges of previous performance PRs. I think this is the last one from me for now.

@joelhawksley
joelhawksley merged commit 19ea003 into ViewComponent:main Nov 4, 2026
27 checks passed
@joelhawksley

Copy link
Copy Markdown
Member

@erikaxel thanks for the fixes! I'm hoping we are close to the point where the caching feature does not have too much of a performance penalty, especially since we've done other things to speed up compilation. If this continues to be an issue, we could look at adding the ability to turn it off, but I'd rather avoid that route if possible.

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