Skip to content

Lazily load erb and ostruct - #435

Merged
tpowell-progress merged 1 commit into
chef:mainfrom
tas50:perf/lazy-erb
Aug 28, 2026
Merged

Lazily load erb and ostruct#435
tpowell-progress merged 1 commit into
chef:mainfrom
tas50:perf/lazy-erb

Conversation

@tas50

@tas50 tas50 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

erb costs ~7 ms to require and pulls in strscan, cgi/escape and the rest of the erb tree — 11 files. It and ostruct are only touched inside Generator::Base.get_script, and only when a template actually needs rendering, so this loads them there rather than at require time.

Drive-by fix

The existing guard never worked:

require "erb" unless defined?(Erb)

The constant is ERB, not Erb, so defined?(Erb) was always nil and the guard never suppressed anything. Corrected to defined?(ERB) as part of the move.

Measurements

Ruby 4.0.6, minimum of 15 fresh subprocesses:

require time loaded features
before 56.85 ms 240
after 49.11 ms 229

7.74 ms and 11 files saved on every consumer's boot.

Verification

  • 576 unit examples and the functional spec pass; chefstyle clean.
  • Verified in a bare process that ERB and OpenStruct are undefined after require "mixlib/install" and defined after the first render.
  • Generated output is unchanged: 176 SHA256 digests covering install.sh, install.ps1, both platform-detection scripts, every product matrix entry and the ScriptGenerator variants are byte-identical to main. This matters here because CI feeds the generated scripts to ShellCheck and PSScriptAnalyzer.

@tas50
tas50 requested review from a team and jaymzh as code owners August 28, 2026 03:55
`erb` costs ~7ms to require and pulls in strscan, cgi/escape and the rest of
the erb tree (11 files). It and `ostruct` are only touched inside
Generator::Base.get_script when a template actually needs rendering, so load
them there rather than at require time.

Also fixes the guard on the erb require: the constant is `ERB`, not `Erb`, so
`unless defined?(Erb)` never matched and the guard never did anything.

Measured on Ruby 4.0.6 (min of 15 fresh subprocesses):

  before   56.85 ms   240 loaded features
  after    49.11 ms   229 loaded features

Generated output is unchanged: 176 SHA256 digests covering install.sh,
install.ps1, the platform detection scripts, every product matrix entry and
the ScriptGenerator variants are byte-identical to main.

Signed-off-by: Tim Smith <tsmith84@proton.me>
@tpowell-progress
tpowell-progress merged commit d87def3 into chef:main Aug 28, 2026
74 checks passed
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