Description
Plush VM Benchmark Description
Overview
This benchmark compares two execution strategies for the same Plush template:
- Tree-walker evaluator path
- VM path
The template includes:
- Variable interpolation
- Conditional rendering
- Loop rendering
Benchmark Template
const testTemplate = `Hello <%= name %>!
<% if (admin) { %><b>Admin</b><% } %>
<%= for (i, v) in items { %><%= v %> <% } %>`
// ── current tree-walker path ──────────────────────────────────────────────────
func BenchmarkTreeWalker(b *testing.B) {
// --- parse once (mirrors vm.Compile) ---
t, err := plush.NewTemplate(testTemplate)
if err != nil {
b.Fatal(err)
}
ctx := plush.NewContext()
ctx.Set("name", "World")
ctx.Set("admin", true)
ctx.Set("items", []string{"a", "b", "c"})
cachedAST := t.Program
if cachedAST == nil {
b.Fatal("parsed AST is nil")
}
if err := t.Parse(); err != nil {
b.Fatal(err)
}
if t.Program != cachedAST {
b.Fatal("AST was reparsed; expected cached program")
}
b.ResetTimer()
// --- tree-walk every request ---
for i := 0; i < b.N; i++ {
_, _, err := t.Exec(ctx)
if err != nil {
b.Fatal(err)
}
}
}
// ── new threaded-interpreter path ─────────────────────────────────────────────
//
// Compile once (simulating what would be stored in the cache),
// then only Execute on each request.
func BenchmarkThreadedInterpreter(b *testing.B) {
// --- compile once ---
program, err := parser.Parse(testTemplate)
if err != nil {
b.Fatal(err)
}
compiled, err := vm.Compile(program)
if err != nil {
b.Fatal(err)
}
ctx := plush.NewContext()
ctx.Set("name", "World")
ctx.Set("admin", true)
ctx.Set("items", []string{"a", "b", "c"})
b.ResetTimer()
// --- execute many times ---
for i := 0; i < b.N; i++ {
_, _, err := vm.Execute(compiled, ctx)
if err != nil {
b.Fatal(err)
}
}
}
What Each Benchmark Measures
BenchmarkTreeWalker
- Parses template once via NewTemplate
- Builds a shared context (name, admin, items)
- Verifies AST reuse by checking Program pointer stability
- Executes evaluator tree-walk on each iteration with Exec(ctx)
Why this matters:
- Confirms parsing is cached and not repeated in the hot loop
- Isolates runtime cost of evaluator execution
BenchmarkThreadedInterpreter
- Parses source once into AST
- Compiles AST once into VM instruction program
- Builds equivalent context (name, admin, items)
- Executes precompiled instruction stream each iteration
Why this matters:
- Measures steady-state runtime when compile cost is paid once
- Reflects cache-friendly production behavior for repeated renders
Fairness Notes
- Both paths use the same template and context data
- Both paths do one-time setup before timing
- Timer starts only after parse/compile setup is complete
- Hot loop measures render execution only
Why VM Is Faster (Summary)
- Flat instruction dispatch avoids repeated AST traversal
- Fewer per-node runtime checks during render
- Lower allocation pressure in hot path
- Better branch and cache locality in execution loop
Expected Outcome
The VM benchmark should show:
- Lower ns/op
- Fewer allocs/op
- Lower B/op
##Results:
Plush VM Benchmark Summary
We benchmarked BenchmarkTreeWalker vs BenchmarkThreadedInterpreter using:
go test ./vm -run '^$' -bench 'Benchmark(TreeWalker|ThreadedInterpreter)$' -benchmem -count=6
Environment:
- OS: Linux
- Arch: amd64
- Package: github.com/gobuffalo/plush/v5/vm
Average results across 6 runs:
| Metric |
TreeWalker |
ThreadedInterpreter |
Improvement |
| ns/op |
3397.17 |
1175.50 |
2.89x faster |
| B/op |
1297 |
376 |
71.01% less |
| allocs/op |
44 |
14 |
68.18% less |
Key takeaway:
The VM Compiler is significantly faster and more memory efficient than the current tree-walker evaluator for this template workload.
Because both benchmarks exclude parse and compile time from the hot loop, the result demonstrates the benefit of a compile-once, execute-many rendering model. In this benchmark, the VM path reduces execution time by roughly 65% and substantially reduces allocation pressure.
This provides strong proof of concept that Plush can benefit meaningfully from a VM-based execution path, especially for cached templates that are rendered repeatedly in production.
Additional Information
No response
Description
Plush VM Benchmark Description
Overview
This benchmark compares two execution strategies for the same Plush template:
The template includes:
Benchmark Template
What Each Benchmark Measures
BenchmarkTreeWalker
Why this matters:
BenchmarkThreadedInterpreter
Why this matters:
Fairness Notes
Why VM Is Faster (Summary)
Expected Outcome
The VM benchmark should show:
##Results:
Plush VM Benchmark Summary
We benchmarked
BenchmarkTreeWalkervsBenchmarkThreadedInterpreterusing:Environment:
Average results across 6 runs:
Key takeaway:
The VM Compiler is significantly faster and more memory efficient than the current tree-walker evaluator for this template workload.
Because both benchmarks exclude parse and compile time from the hot loop, the result demonstrates the benefit of a compile-once, execute-many rendering model. In this benchmark, the VM path reduces execution time by roughly 65% and substantially reduces allocation pressure.
This provides strong proof of concept that Plush can benefit meaningfully from a VM-based execution path, especially for cached templates that are rendered repeatedly in production.
Additional Information
No response