Skip to content

Cache resolved lazy children instead of rebuilding child parsers on every parse #577

Description

@stalep

Updated Understanding (2026-08-17)

Investigation revealed that lazy child resolution already caches correctly within a single container lifetime -- resolveAllLazyChildren() resolves all children once, stores them in childParsers, then clears lazyChildClasses. Subsequent calls are no-ops. The maps and child parsers persist across clear() calls between parses.

The actual optimization opportunity is: resolve children eagerly during container construction rather than deferring to the first parse. Currently, the first executeCommand() triggers child resolution via findChildParserException() -> getAllChildParsers() -> resolveAllLazyChildren(). This adds latency to the user-visible first-command response time.

Problem

When AeshCommandContainerBuilder.buildFromProvider() creates a container for a group command like JBangCommand, it registers child classes as lazy references via addLazyChildWithAliases(). The actual child ProcessedCommand objects (with all their options, lookup maps, etc.) are not created until the first time getAllChildParsers() is called.

For the jbang-like CLI with 11 subcommands, this means the first executeCommand() call pays the cost of:

Profile Evidence

async-profiler CPU profiling (generated path, 10k full-startup iterations):

  • resolveAllLazyChildren fan-out: ~5% of full CPU (199 samples out of 3,897)
  • Triggered from findChildParserException -> getAllChildParsers on the parse path

Proposed Fix

Resolve lazy children eagerly in AeshCommandContainerBuilder.buildFromProvider() after the container is fully constructed:

// In buildFromProvider(), after addLazyChildWithAliases() calls:
parser.resolveAllLazyChildren();  // resolve eagerly instead of waiting for first parse

Or alternatively, call it in AeshCommandContainer construction or in MutableCommandRegistryImpl.addCommand().

This moves the child resolution cost from the first executeCommand() to container construction, where it is part of the expected initialization overhead.

Impact

  • Eliminates ~5% of CPU from the first-command parse path
  • No behavior change -- children are resolved identically, just earlier
  • Zero risk -- the guard clause in resolveAllLazyChildren() already handles double-calls safely
  • Benefits single-shot CLI tools (AeshRuntimeRunner) where the first command IS the only command

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions