Skip to content

Commit 07f299e

Browse files
committed
feat(benchmark): add DVE view rendering benchmarks ➿
- Add DVE benchmark route handlers for /test-view* - Add DVE benchmark templates under benchmark/views - Configure benchmark routers with viewsDir - Document DVE view benchmark routes and commands
1 parent 2b6bd44 commit 07f299e

12 files changed

Lines changed: 97 additions & 41 deletions

benchmark/README.md

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,112 @@
11
# Performance Benchmarks
22

3-
## Test Environment
3+
Benchmarks for the Deserve router + DVE view rendering.
44

5-
- **Tool:** `autocannon`[npmjs.com/package/autocannon](https://www.npmjs.com/package/autocannon)
6-
- **Configuration:** 500 connections, 10 pipelining, 30 seconds (adjust per run)
5+
## Setup
76

8-
## How to Run
7+
- **Load tool**: `autocannon` ([npm](https://www.npmjs.com/package/autocannon))
8+
- **Default config used in this doc**: 500 connections, pipelining 10, duration 30s
99

10-
**1. Start the server** (from repo root). Pick one:
10+
## Quick Start
1111

12-
- **Without worker**`/test` and `/test-cpu` only; `/test-worker` returns 503:
12+
Start a server (from repo root), then run `autocannon` from another terminal.
13+
14+
### Start Server
15+
16+
- **Non-worker mode** (view routes + CPU on main thread):
1317

1418
```bash
1519
deno run --allow-net --allow-read benchmark/main.ts
1620
```
1721

18-
- **With worker** — all routes including `/test-worker`:
22+
- **Worker mode** (enables `/test-worker`):
23+
1924
```bash
2025
deno run --allow-net --allow-read benchmark/main-worker.ts
2126
```
2227

23-
**2. Run autocannon** (in another terminal; requires Node/npx):
28+
Notes:
29+
30+
- In non-worker mode, **`/test-worker` returns 503** (`worker not enabled`).
31+
- All view routes (`/test-view*`) are available in both modes.
32+
33+
### Run Benchmark
2434

2535
```bash
2636
npx autocannon http://localhost:8000/test -c 500 -p 10 -d 30
2737
```
2838

29-
## Files
39+
## Routes
40+
41+
### JSON + CPU Comparison
3042

31-
- **`benchmark/main.ts`** — Router without worker. Serves `/test`, `/test-cpu`.
32-
- **`benchmark/main-worker.ts`** — Router with worker pool (inline CPU loop).
33-
- **`benchmark/routes/test.ts`** — GET `/test`: baseline, JSON only (no CPU work).
34-
- **`benchmark/routes/test-cpu.ts`** — GET `/test-cpu`: same CPU work (50k sqrt loop) on **main thread**.
35-
- **`benchmark/routes/test-worker.ts`** — GET `/test-worker`: same CPU work offloaded to **worker**.
43+
| Route | What it does | Why it exists |
44+
| -------------- | ---------------------------------------- | -------------------------- |
45+
| `/test` | JSON only (no CPU work) | Baseline throughput |
46+
| `/test-cpu` | 50k `sqrt` loop on **main thread** | Event-loop blocking cost |
47+
| `/test-worker` | Same loop offloaded to a **worker pool** | Offload overhead + scaling |
3648

37-
## Routes for comparison
49+
### DVE View Engine Routes
3850

39-
| Route | Description | Use case |
40-
| -------------- | ---------------------------- | -------------------- |
41-
| `/test` | JSON only, no CPU | Baseline throughput |
42-
| `/test-cpu` | 50k sqrt loop on main thread | Main-thread CPU cost |
43-
| `/test-worker` | Same loop in worker pool | Worker offload cost |
51+
| Route | What it renders | Why it exists |
52+
| ---------------------- | ------------------------------- | ---------------------------- |
53+
| `/test-view` | Simple variable render | DVE baseline |
54+
| `/test-view-each-meta` | `each` block with metadata | Loop + expression evaluation |
55+
| `/test-view-include` | Include + partial | Composition overhead |
56+
| `/test-view-expr` | Optional chaining + expressions | Expression parser/evaluator |
4457

45-
Run autocannon against each route (in another terminal):
58+
## Commands (Copy/Paste)
4659

4760
```bash
48-
# Baseline (no CPU)
61+
# JSON Baseline
4962
npx autocannon http://localhost:8000/test -c 500 -p 10 -d 30
5063

51-
# CPU on main thread (blocks event loop)
64+
# CPU On Main Thread
5265
npx autocannon http://localhost:8000/test-cpu -c 500 -p 10 -d 30
5366

54-
# CPU in worker (non-blocking)
67+
# CPU In Worker (Requires benchmark/main-worker.ts)
5568
npx autocannon http://localhost:8000/test-worker -c 500 -p 10 -d 30
69+
70+
# DVE Views
71+
npx autocannon http://localhost:8000/test-view -c 500 -p 10 -d 30
72+
npx autocannon http://localhost:8000/test-view-each-meta -c 500 -p 10 -d 30
73+
npx autocannon http://localhost:8000/test-view-include -c 500 -p 10 -d 30
74+
npx autocannon http://localhost:8000/test-view-expr -c 500 -p 10 -d 30
5675
```
5776

58-
## Latest benchmark results (30s, 500 conn, pipelining 10)
77+
## Latest Results (30s, 500 Conn, Pipelining 10)
5978

60-
**Non-worker**`main.ts`:
79+
### JSON + CPU (Example)
6180

62-
| Route | Test 1 | Test 2 | Test 3 | Req/Sec (avg) | Latency (avg) | Total (avg) |
63-
| -------------- | ------- | ------- | ------- | ------------- | ------------- | ----------- |
64-
| `/test` | 175,343 | 170,567 | 164,515 | 170,142 | 29 ms | 5109k |
65-
| `/test-cpu` | 25,009 | 24,949 | 24,927 | 24,962 | 199 ms | 754k |
66-
| `/test-worker` | 150,151 | 148,683 | 148,061 | 148,965 | 33 ms | 4474k |
81+
**Non-worker**`benchmark/main.ts`:
6782

68-
`/test-worker` without worker returns 503 (no pool).
83+
| Route | Test 1 | Test 2 | Test 3 | Req/Sec (avg) | Latency (avg) | Total (avg) |
84+
| ----------- | ------- | ------- | ------- | ------------- | ------------- | ----------- |
85+
| `/test` | 175,343 | 170,567 | 164,515 | 170,142 | 29 ms | 5109k |
86+
| `/test-cpu` | 25,009 | 24,949 | 24,927 | 24,962 | 199 ms | 754k |
6987

70-
**With worker**`main-worker.ts` (worker pool, poolSize 4).
88+
**Worker-enabled**`benchmark/main-worker.ts`:
7189

7290
| Route | Test 1 | Test 2 | Test 3 | Req/Sec (avg) | Latency (avg) | Total (avg) |
7391
| -------------- | ------- | ------- | ------- | ------------- | ------------- | ----------- |
7492
| `/test` | 174,259 | 178,487 | 170,834 | 174,527 | 28 ms | 5.24M |
7593
| `/test-cpu` | 25,133 | 24,833 | 24,773 | 24,912 | 199 ms | 752k |
7694
| `/test-worker` | 69,265 | 69,063 | 68,810 | 69,046 | 72 ms | 2076k |
7795

78-
**Conclusion (test-cpu vs test-worker).** Both routes run the same CPU-bound workload (50k sqrt loop). `/test-cpu` runs it on the main thread and blocks the event loop (~25k req/s, ~199 ms). `/test-worker` offloads the work to a worker pool (~69k req/s, ~72 ms). For CPU-bound tasks, using the worker yields roughly 2.8× higher throughput and ~2.8× lower latency because the main thread stays free to accept and dispatch requests.
96+
Takeaway: `/test-cpu` blocks the event loop; `/test-worker` moves the same work off-thread.
7997

80-
## Test Behavior (baseline)
98+
### Views (DVE Rendering Baseline)
99+
100+
| Route | Test 1 | Test 2 | Test 3 | Req/Sec (avg) | Latency (avg) | Total (avg) |
101+
| ---------------------- | ------- | ------- | ------- | ------------- | ------------- | ----------- |
102+
| `/test-view` | 129,063 | 124,978 | 120,516 | 124,852 | 39.77 ms | 3.75M |
103+
| `/test-view-each-meta` | 10,406 | 10,381 | 10,073 | 10,287 | 482.13 ms | 313k |
104+
| `/test-view-include` | 112,115 | 109,558 | 101,886 | 107,853 | 46.02 ms | 3.24M |
105+
| `/test-view-expr` | 96,328 | 94,207 | 82,094 | 90,876 | 54.84 ms | 2.73M |
106+
107+
## Files
81108

82-
- **Method:** GET
83-
- **Route:** `/test`
84-
- **Response:** `{ "hello": "world!" }` (JSON)
85-
- **File-based routing:** `benchmark/routes/test.ts` → pattern `/test`
86-
- **API:** `Context` + `ctx.send.json()`
109+
- `benchmark/main.ts`: server entry (non-worker)
110+
- `benchmark/main-worker.ts`: server entry (worker-enabled)
111+
- `benchmark/routes/*.ts`: route handlers
112+
- `benchmark/views/*.dve`: DVE templates

benchmark/main-worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const workerScriptUrl = URL.createObjectURL(
1818

1919
const router = new Router({
2020
routesDir: 'benchmark/routes',
21+
viewsDir: 'benchmark/views',
2122
worker: { scriptURL: workerScriptUrl, poolSize: 4 }
2223
})
2324

benchmark/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Router } from '@app/index.ts'
22

3-
const router = new Router({ routesDir: 'benchmark/routes' })
3+
const router = new Router({ routesDir: 'benchmark/routes', viewsDir: 'benchmark/views' })
44

55
await router.serve(8000)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Context } from '@app/index.ts'
2+
3+
export async function GET(ctx: Context) {
4+
const items = Array.from({ length: 50 }, (_, index) => index)
5+
return await ctx.render('each-meta', { items })
6+
}

benchmark/routes/test-view-expr.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Context } from '@app/index.ts'
2+
3+
export async function GET(ctx: Context) {
4+
const user = { name: 'Nea', isAdmin: true }
5+
return await ctx.render('expr', { user })
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Context } from '@app/index.ts'
2+
3+
export async function GET(ctx: Context) {
4+
return await ctx.render('include', { name: 'Nea' })
5+
}

benchmark/routes/test-view.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Context } from '@app/index.ts'
2+
3+
export async function GET(ctx: Context) {
4+
return await ctx.render('hello', { name: 'World' })
5+
}

benchmark/views/each-meta.dve

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{#each items as item}}({{ @index }}/{{ @length }} {{#if @first}}F{{else}}-{{/if}}{{#if @last}}L{{else}}-{{/if}}={{ item }});{{/each}}

benchmark/views/expr.dve

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Hello {{ user?.name ?? 'Guest' }}.
2+
{{#if user?.isAdmin ? true : false}}ADMIN{{else}}USER{{/if}}
3+
Sum={{ 1 + 2 * 3 }}

benchmark/views/hello.dve

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello {{ name }}.

0 commit comments

Comments
 (0)