Problem
A new AeshConverterInvocation wrapper object is created for every option value that needs type conversion during ProcessedOption.doConvert(). The invocation object wraps the input string, aeshContext, and a few other fields. Since conversion is single-threaded per parse cycle, the same invocation object could be reused.
Profile Evidence
async-profiler alloc profiling of the jbang-like benchmark (generated path, full startup):
AeshConverterInvocation allocation: 1.2% of full allocations (23 samples)
Proposed Fix
Two options:
-
Mutable reuse: Make AeshConverterInvocation mutable with a reset(String input, AeshContext ctx) method. Store one instance per ProcessedCommand or per AeshCommandPopulator and reuse it across conversions within a single parse cycle.
-
ThreadLocal: Use a ThreadLocal<AeshConverterInvocation> to avoid allocating a new instance per conversion. Since parsing is always single-threaded per command, a thread-local provides safe reuse without explicit lifecycle management.
Option 1 is preferred as it avoids ThreadLocal overhead and is more explicit.
Impact
Eliminates ~1.2% of full startup allocations. Small but easy win with minimal code change.
Problem
A new
AeshConverterInvocationwrapper object is created for every option value that needs type conversion duringProcessedOption.doConvert(). The invocation object wraps the input string, aeshContext, and a few other fields. Since conversion is single-threaded per parse cycle, the same invocation object could be reused.Profile Evidence
async-profiler alloc profiling of the jbang-like benchmark (generated path, full startup):
AeshConverterInvocationallocation: 1.2% of full allocations (23 samples)Proposed Fix
Two options:
Mutable reuse: Make
AeshConverterInvocationmutable with areset(String input, AeshContext ctx)method. Store one instance perProcessedCommandor perAeshCommandPopulatorand reuse it across conversions within a single parse cycle.ThreadLocal: Use a
ThreadLocal<AeshConverterInvocation>to avoid allocating a new instance per conversion. Since parsing is always single-threaded per command, a thread-local provides safe reuse without explicit lifecycle management.Option 1 is preferred as it avoids ThreadLocal overhead and is more explicit.
Impact
Eliminates ~1.2% of full startup allocations. Small but easy win with minimal code change.