Motivation
The AVM interpreter is ~4.4x slower than gawk on a tight arithmetic loop (measured on the PR #534 branch, but this is long-standing and unrelated to runtime typing):
BEGIN { s=0; for(i=0;i<5000000;i++) s=s+i; print s }
- gawk: ~1.5 s
- jawk: ~6.5 s
Problem
Every opcode pushes/pops operands through Deque<Object> operandStack = new ArrayDeque<Object>() (AVM.java). Each access pays:
ArrayDeque.push/pop call overhead: internal head-index wraparound math, capacity checks, and null checks
- a
NULL_OPERAND sentinel mapping on both push() and pop() (AVM.pop()/AVM.push())
A typical statement like s = s + i executes 4-6 opcodes, each with 1-2 stack operations, so this overhead multiplies across every executed tuple.
Proposal
Replace the deque with a plain Object[] and an explicit top-of-stack index owned by the AVM:
push/pop/peek become single array accesses that the JIT can fully inline
- grow the array on demand (operand stack depth is bounded by expression nesting, so it stays small)
- the
NULL_OPERAND sentinel can likely be dropped since the AVM controls all accesses and can allow null slots directly
The operand stack is fully encapsulated behind push()/pop() in AVM (with a couple of operandStack.clear() call sites), so this is a contained change.
Note
popArguments() and print/printf argument gathering also allocate an Object[] per call; worth revisiting in the same pass where they sit on hot paths.
Measurement
Use the project's existing JMH harness rather than wall-clock CLI timings:
mvn package -Pbenchmark -DskipTests
java -jar target/jawk-*-benchmarks.jar
src/jmh/java/io/jawk/backend/AVMExpressionBenchmark.java already exercises the interpreter's expression evaluation and is the right place to anchor before/after numbers (add a tight arithmetic-loop case there if not already covered). AwkScriptBenchmark gives the end-to-end view. The gawk-vs-jawk CLI numbers above are motivation only — JMH results are the acceptance criterion.
🤖 Generated with Claude Code
Motivation
The AVM interpreter is ~4.4x slower than gawk on a tight arithmetic loop (measured on the PR #534 branch, but this is long-standing and unrelated to runtime typing):
Problem
Every opcode pushes/pops operands through
Deque<Object> operandStack = new ArrayDeque<Object>()(AVM.java). Each access pays:ArrayDeque.push/popcall overhead: internal head-index wraparound math, capacity checks, and null checksNULL_OPERANDsentinel mapping on bothpush()andpop()(AVM.pop()/AVM.push())A typical statement like
s = s + iexecutes 4-6 opcodes, each with 1-2 stack operations, so this overhead multiplies across every executed tuple.Proposal
Replace the deque with a plain
Object[]and an explicit top-of-stack index owned by the AVM:push/pop/peekbecome single array accesses that the JIT can fully inlineNULL_OPERANDsentinel can likely be dropped since the AVM controls all accesses and can allow null slots directlyThe operand stack is fully encapsulated behind
push()/pop()in AVM (with a couple ofoperandStack.clear()call sites), so this is a contained change.Note
popArguments()and print/printf argument gathering also allocate anObject[]per call; worth revisiting in the same pass where they sit on hot paths.Measurement
Use the project's existing JMH harness rather than wall-clock CLI timings:
mvn package -Pbenchmark -DskipTests java -jar target/jawk-*-benchmarks.jarsrc/jmh/java/io/jawk/backend/AVMExpressionBenchmark.javaalready exercises the interpreter's expression evaluation and is the right place to anchor before/after numbers (add a tight arithmetic-loop case there if not already covered).AwkScriptBenchmarkgives the end-to-end view. The gawk-vs-jawk CLI numbers above are motivation only — JMH results are the acceptance criterion.🤖 Generated with Claude Code