#29818

v3, veb: route dispatch is monomorphized per controller — 16 controllers × 8 routes → 42k generated C lines, ~0.11 s monomorphize per controller

OpenJengro777opened 21d ago

Summary

Every controller type in a veb app produces its own handle_route[A, X] instantiation containing the compile-time expansion of that controller's routes. Growth is linear in the number of controllers and routes:

  • 2 controllers × 1 route → 3 handle_route instances, monomorphize 1.56 s
  • 16 controllers × 8 routes → 17 instances, monomorphize 3.25 s, 42,088 veb C lines

Slope: ~0.11 s of monomorphization and ~2.5k C lines per controller (8 routes), i.e. ~14 ms and ~315 C lines per route, on top of a ~1.5 s framework baseline.

Environment

  • V 0.5.2 5bd6709 (vlang/v master), Linux x86_64
  • v -new-compiler -cc tcc -no-retry-compilation, compile to C with -o out.c
  • 2026-10-09, ±5% run-to-run variance

Minimal case (single file, 2 controllers × 1 route)

module main

import veb

struct Context {
	veb.Context
}

struct App {
	veb.Middleware[Context]
	veb.Controller
}

struct C0 {
	veb.Middleware[Context]
}

@['/c0/r0'; get]
pub fn (app &C0) h0_0(mut ctx Context) veb.Result {
	return ctx.text('c0r0')
}

struct C1 {
	veb.Middleware[Context]
}

@['/c1/r0'; get]
pub fn (app &C1) h1_0(mut ctx Context) veb.Result {
	return ctx.text('c1r0')
}

fn main() {
	mut app := &App{}
	app.register_controller[C0, Context]('/c0', mut &C0{}) or { panic(err) }
	app.register_controller[C1, Context]('/c1', mut &C1{}) or { panic(err) }
	veb.run[App, Context](mut app, 18099)
}
$ v -new-compiler -cc tcc -no-retry-compilation -show-timings -o out.c minimal.v
  monomorphize          1555.86 ms
  cgen (parallel)        553.19 ms
  total                 4124.12 ms
    generated C size             4042933 bytes

grep -oE 'veb__handle_route_T_[A-Za-z_0-9]+' out.c | sort -u | wc -l → 3 (one per controller + one for the app itself).

Scaling reproduction

#!/bin/sh
# Usage: sh gen.sh 16 8 > main.v   (N controllers × M routes)
N=${1:-16}
M=${2:-8}
cat <<'HDR'
module main

import veb

struct Context {
	veb.Context
}

struct App {
	veb.Middleware[Context]
	veb.Controller
}
HDR
c=0
while [ "$c" -lt "$N" ]; do
	cat <<EOF

struct C$c {
	veb.Middleware[Context]
}
EOF
	r=0
	while [ "$r" -lt "$M" ]; do
		cat <<EOF

@['/c$c/r$r'; get]
pub fn (app &C$c) h${c}_${r}(mut ctx Context) veb.Result {
	return ctx.text('c${c}r${r}')
}
EOF
		r=$((r + 1))
	done
	c=$((c + 1))
done
echo ''
echo 'fn main() {'
echo '	mut app := &App{}'
c=0
while [ "$c" -lt "$N" ]; do
	echo "	app.register_controller[C$c, Context]('/c$c', mut &C$c{}) or { panic(err) }"
	c=$((c + 1))
done
echo '	veb.run[App, Context](mut app, 18099)'
echo '}'
$ sh gen.sh 16 8 > main.v
$ v -new-compiler -cc tcc -no-retry-compilation -show-timings -o out.c main.v

  monomorphize          3253.68 ms      344 MB RSS      597 MB stage peak
  cgen (parallel)        855.53 ms      299 MB RSS      490 MB stage peak
  total                 6095.14 ms
    generated C size             6443690 bytes
controllers × routeshandle_route instancesveb C linesmonomorphizecgentotalgenerated C
2 × 13—1.56 s0.55 s4.12 s4.04 MB
2 × 836,8361.68 s0.59 s4.28 s4.29 MB
8 × 8921,9442.36 s0.67 s5.02 s5.21 MB
16 × 81742,0883.25 s0.86 s6.10 s6.44 MB

Where the cost is

  • vlib/veb/veb.v:323 handle_route[A, X] — per-controller monomorphized dispatch (the bulk of the generated code)
  • vlib/veb/veb.v:46 generate_routes[A, X] — also instantiated per controller
  • vlib/veb/controller.v:81 check_duplicate_routes_in_controllers[T], controller.v:25 register_controller[A, X]

Expected

Route dispatch could be table-driven: one small function per route plus a data table, instead of expanding the full controller dispatch into a monomorphized handle_route per controller. Users can't work around this by merging controllers — the per-route code is the bulk and remains.

Impact

Real app: 34 controllers / 198 routes → veb__handle_route instantiations total 61k C lines (largest single instance 8354 lines for a ~30-route controller; Pay: 5439 lines). This is per-build cost on top of the framework baseline.

Notes

  • Steady-state codegen volume; not the memory blow-up in #29304 (fixed by #29311) or the non-convergence in #29764 (fixed).
  • Happy to provide the generator/output for any N × M if that helps.

Activity

0

No activity yet.