Skip to content

Engine: Compute relative file paths lazily - #2262

Merged
marcoroth merged 2 commits into
marcoroth:mainfrom
joelhawksley:joelhawksley-lazy-relative-file-path
Aug 17, 2026
Merged

Engine: Compute relative file paths lazily#2262
marcoroth merged 2 commits into
marcoroth:mainfrom
joelhawksley:joelhawksley-lazy-relative-file-path

Conversation

@joelhawksley

Copy link
Copy Markdown
Contributor

What

Compute VisitorContext#relative_file_path on demand instead of eagerly during
context construction.

Herb::Engine creates a context for every template, but the relative path is
primarily consumed by diagnostics, overlays, debug output, and visitors that
explicitly request it. Valid templates compiled without those features
previously still paid for Pathname#absolute?, path joining,
Pathname#relative_path_from, and #to_s.

The public behavior is unchanged: relative_file_path, context hash access,
merging, inspection, and serialization still return the same value. A
regression test verifies that derivation does not happen during initialization.

Split out of #1872.

Benchmark

Measured Herb::Engine compilation over
marcoroth/herb-corpus at
5560d823, using Ruby 4.0.2.

The benchmark compiled the 36,046 corpus templates accepted by both variants
with a filename and project path, escape: true, no visitors, non-strict
parsing, and Ruby validation disabled. Results are medians of three full-corpus
runs:

metric main (cc3eb8bc) this branch delta
allocated objects 88,907,732 79,115,256 -9,792,476 (-11.0%)
wall time 9.21s 7.94s -13.8%

Generated Ruby was byte-identical in every run
(aee36910bcd428792272440a7d39cabcd14c641544c6e35e2b610a14fbd9892b).

Avoid normalizing each template filename during context construction when compilation does not consume the relative path.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ccc00bcf-145f-4f65-8675-5d004f02dd98
@github-actions github-actions Bot added ruby Ruby source for the gem and its libraries rbs RBS type signatures in sig/ engine Herb engine and Rails template compilation rubygem The herb RubyGem and its packaging labels Aug 17, 2026
@joelhawksley
joelhawksley marked this pull request as ready for review August 17, 2026 20:20
Comment thread lib/herb/engine/visitor_context.rb Outdated
@marcoroth marcoroth added the optimization Compile-time and generated-output optimizations label Aug 17, 2026
@marcoroth marcoroth added this to the v0.11.0 milestone Aug 17, 2026
Cache the lazily derived path through a private mutable holder so VisitorContext can remain frozen.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 30890d9a-a37a-4e59-b7bc-f23153d5768a
@file_path = self.class.coerce_file_path(file_path)
@project_path = self.class.coerce_project_path(project_path)
@relative_file_path = self.class.derive_relative_file_path(@file_path, @project_path)
@relative_file_path_cache = [] #: Array[String]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcoroth this approach keeps VisitorContext frozen, FYI.

@joelhawksley
joelhawksley requested a review from marcoroth August 17, 2026 21:26

@marcoroth marcoroth left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @joelhawksley! 馃檹馃徏

@marcoroth
marcoroth enabled auto-merge (squash) August 17, 2026 21:28
@marcoroth
marcoroth merged commit 6ca2914 into marcoroth:main Aug 17, 2026
19 of 24 checks passed
@joelhawksley
joelhawksley deleted the joelhawksley-lazy-relative-file-path branch August 17, 2026 21:37
marcoroth added a commit that referenced this pull request Aug 20, 2026
This pull request stops `VisitorContext` from asking the operating system
where it is for every template it builds a context for.

`coerce_project_path` falls back to `Pathname.new(Dir.pwd)` when no project
path is given, and it ran during construction. The project path is only
read when a visitor, a diagnostic, an overlay or the relative file path
asks for it, so a template compiled without any of those still paid for a
`getcwd` and two allocations.

Deriving it on demand takes about 7% off compile time over the `examples/`
corpus, measured A/B interleaved against `main`.

The value is unchanged. `coerce_project_path` keeps its behavior for a
caller that passes a path, and the derived one is cached through the same
mutable holder #2262 introduced for the relative file path, so the context
can stay frozen.

Follow-up to #2262, which left this the last eagerly computed field on the
context.
marcoroth added a commit that referenced this pull request Aug 20, 2026
This pull request stops `VisitorContext` from asking the operating
system where it is for every template it builds a context for.

`coerce_project_path` falls back to `Pathname.new(Dir.pwd)` when no
project path is given, and it ran during construction. The project path
is only read when a visitor, a diagnostic, an overlay or the relative
file path asks for it, so a template compiled without any of those still
paid for a `getcwd` and two allocations.

Deriving it on demand takes about 7% off compile time over the
`examples/` corpus.

The value is unchanged. `coerce_project_path` keeps its behavior for a
caller that passes a path, and the derived one is cached through the
same mutable holder #2262 introduced for the relative file path, so the
context can stay frozen.

Follow-up to #2262.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine Herb engine and Rails template compilation optimization Compile-time and generated-output optimizations rbs RBS type signatures in sig/ ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants