Measure and compare bytecode impact of macro/codegen changes on compiled Scala class files. Use this skill whenever investigating bytecode size, method count, class file overhead, lazy val machinery, LazyRef/lzyINIT patterns, or comparing "before vs after" bytecode output of a code change. Also triggers on "bytecode comparison", "prove bytecode improvement", "how much bytecode", "class file size", "method count", "bytecode regression", "lazy val overhead", or "does this change reduce bytecode". Use this skill alongside compile-time and runtime benchmarks for a complete picture of a change's impact.
Measure the bytecode footprint of compiled Scala code and compare before/after to prove (or disprove) that a change reduces generated bytecode.
val instead of lazy val, removing wrapper classes, simplifying generated code)LazyRef, lzyINIT, monitorenter (lazy val synchronization)python3 .claude/skills/bytecode-impact/scripts/analyze_bytecode.py \
out/benchmark/sanely/compile.dest/classes
python3 .claude/skills/bytecode-impact/scripts/analyze_bytecode.py \
results/bytecode-impact/classes-after results/bytecode-impact/classes-before \
--labels "With Change" "Without Change"
rm -rf out/benchmark/sanely
./mill benchmark.sanely.compile
Copy or note the classes directory:
mkdir -p results/bytecode-impact
cp -r out/benchmark/sanely/compile.dest/classes results/bytecode-impact/classes-after
Edit the source to revert (e.g., force lazy val for all types), recompile the library, then recompile the benchmark:
./mill sanely.jvm.compile
rm -rf out/benchmark/sanely
./mill benchmark.sanely.compile
cp -r out/benchmark/sanely/compile.dest/classes results/bytecode-impact/classes-before
Then restore the original code and recompile again.
python3 .claude/skills/bytecode-impact/scripts/analyze_bytecode.py \
results/bytecode-impact/classes-after results/bytecode-impact/classes-before \
--labels "With Change" "Without Change"
For a detailed look at a specific class:
javap -c -p out/benchmark/sanely/compile.dest/classes/benchmark/SomeType$.class
Look for:
LazyRef / lzyINIT — lazy val initialization machinerymonitorenter / monitorexit — synchronization blocks_self*$1 / _self*$lzyINIT1$1 — self-referencing methods for recursive types$$anon$N.class files)The bundled scripts/analyze_bytecode.py script collects:
| Metric | What it measures |
|---|---|
| Class files | Number of .class files |
| Total bytecode | Sum of all .class file sizes in bytes |
| Total methods | Method declarations found by javap -p |
| Pattern counts | Occurrences of configurable patterns in disassembled bytecode |
Default patterns: LazyRef, lzyINIT, monitorenter, monitorexit — these indicate lazy val overhead.
Override with --patterns:
python3 .claude/skills/bytecode-impact/scripts/analyze_bytecode.py \
out/benchmark/sanely/compile.dest/classes \
--patterns "LazyRef" "lzyINIT" "monitorenter" "invokedynamic" "anon"
Save full disassembly for manual grep/inspection:
python3 .claude/skills/bytecode-impact/scripts/analyze_bytecode.py \
out/benchmark/sanely/compile.dest/classes \
--dump-javap results/bytecode-impact/javap-full.txt
This workflow works for any Mill module, not just the benchmark. Adjust the compile target:
# Auto derivation benchmark
./mill benchmark.sanely.compile
# → out/benchmark/sanely/compile.dest/classes
# Configured derivation benchmark
./mill benchmark-configured.sanely.compile
# → out/benchmark-configured/sanely/compile.dest/classes
# Unit tests (to see impact on test bytecode)
./mill sanely.jvm.compile
# → out/sanely/jvm/compile.dest/classes