Compile each PS1 template once instead of on every render - #21
Merged
Merged
Conversation
Scripts.render reads the .ps1.erb off disk and builds a fresh Erubi::Engine
every time it is called. The templates ship inside the gem and cannot change
while it is loaded, so all of that repeats identically on each call.
Every remote operation goes through here -- exists?, create_dir, delete,
checksum, check_files and extract_files render once each, and download renders
once per chunk, so a large transfer re-reads and recompiles download.ps1.erb
thousands of times.
The compiled Erubi source is now memoized per template name. The eval still
happens per call, since that is what binds the caller's locals; only the file
read and the compile are hoisted.
200 renders of exists.ps1.erb:
File.read calls main 200 branch 1
5000 renders of download.ps1.erb, median of 5 (Ruby 4.0.6, macOS arm64):
main 403.9 ms (80.8 us each)
branch 136.2 ms (27.2 us each) 2.97x faster
All seven templates verified byte-identical, along with the Binding context
form and the ArgumentError raised for an unsupported context type.
Signed-off-by: Tim Smith <tsmith84@proton.me>
tpowell-progress
approved these changes
Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Scripts.renderreads the.ps1.erbfile off disk and constructs a freshErubi::Engineon every call:The templates ship inside the gem and cannot change while it is loaded, so the read and the compile produce an identical result each time.
Every remote operation goes through here —
exists?,create_dir,delete,checksum,check_filesandextract_filesrender once each, anddownloadrenders once per chunk, so a large transfer re-reads and recompilesdownload.ps1.erbthousands of times.The compiled Erubi source is now memoized per template name. The
evalstill happens per call, since that is what binds the caller's locals — only the file read and the compile are hoisted out.Results
On magnitude, so you can judge whether this is worth carrying: 54 µs per call is nothing next to a WinRM round-trip, so this will not show up as a faster transfer. The argument for it is the eliminated filesystem read on every single remote operation and the per-call
Erubi::Engineallocation, not wall-clock on the wire. If you would rather not carry a module-level cache for that, closing this is a perfectly reasonable call — the other performance PRs stand on their own.Verification
All seven templates render byte-identically to
main, checked by SHA256 of the output, with contexts exercising spaces, PowerShell$env:variables, non-ASCII paths and multi-line hash tables:Identical on both sides. Each template is also rendered twice per run to confirm the cache is not mutated between calls, and the
BindingandNilClasscontext branches are unchanged.One note:
COMPILED[template] ||= ...is not guarded by a mutex. Under MRI a concurrent race would at worst compile the same template twice and store the same value, so it is benign, but flagging it in case you would prefer it locked.