From 25a806a7f69b5da25361cb832eebc2e915789121 Mon Sep 17 00:00:00 2001 From: Tunay <121901995+Tuntii@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:06:59 +0300 Subject: [PATCH 1/8] viltrum: 1.0 zero-allocation core, standard mode --- frameworks/viltrum/Dockerfile | 2 +- frameworks/viltrum/README.md | 10 ++-- frameworks/viltrum/main.v | 95 ++++++++++++----------------------- frameworks/viltrum/meta.json | 6 +-- 4 files changed, 40 insertions(+), 73 deletions(-) diff --git a/frameworks/viltrum/Dockerfile b/frameworks/viltrum/Dockerfile index 00c70a3036..546df9c5ee 100644 --- a/frameworks/viltrum/Dockerfile +++ b/frameworks/viltrum/Dockerfile @@ -9,7 +9,7 @@ RUN curl -fsSL -o /tmp/v.zip https://github.com/vlang/v/releases/download/0.5.2/ unzip -q /tmp/v.zip -d /opt && rm /tmp/v.zip ENV PATH=/opt/v:$PATH -ARG VILTRUM_REF=v0.13.0 +ARG VILTRUM_REF=v1.0.0 RUN git clone --depth 1 --branch ${VILTRUM_REF} https://github.com/Tuntii/viltrum /root/.vmodules/viltrum WORKDIR /app diff --git a/frameworks/viltrum/README.md b/frameworks/viltrum/README.md index 6289d8bf03..0800822e06 100644 --- a/frameworks/viltrum/README.md +++ b/frameworks/viltrum/README.md @@ -3,12 +3,10 @@ [viltrum](https://github.com/Tuntii/viltrum) is an HTTP framework for V with its own HTTP/1.1 engine (no stdlib `net.http` underneath). Docs: https://github.com/Tuntii/viltrum/tree/main/docs -This entry runs one process with one epoll loop per CPU (`epoll_cores`, `SO_REUSEPORT`), which is -why it is `tuned`: the framework default is thread-per-connection. +This entry runs viltrum with its default configuration (standard mode). | Profile | Endpoint | Notes | |---------|----------|-------| -| baseline, limited-conn | `GET/POST /baseline11` | query params via `req.query_param`, body via `req.body` (Content-Length or chunked) | -| pipelined | `GET /pipeline` | pipelined responses are batched into one write by the engine | -| json-comp | `GET /json/:count?m=` | `json.encode` per request, `viltrum.gzip` middleware | -| echo-ws | `/ws` | `app.ws`, viltrum's own WebSocket implementation | +| baseline, limited-conn | `GET/POST /baseline11` | `c.query_int`, `c.body_int` (Content-Length or chunked) | +| pipelined | `GET /pipeline` | the engine batches pipelined responses into one write | +| json-comp | `GET /json/:count?m=` | `c.json` per request, `viltrum.gzip` middleware | diff --git a/frameworks/viltrum/main.v b/frameworks/viltrum/main.v index 1d911d2bf4..26354454df 100644 --- a/frameworks/viltrum/main.v +++ b/frameworks/viltrum/main.v @@ -2,7 +2,6 @@ module main import json import os -import runtime import viltrum struct Rating { @@ -19,85 +18,55 @@ struct Item { active bool tags []string rating Rating +mut: + total i64 } -struct PricedItem { - id int - name string - category string - price int - quantity int - active bool - tags []string - rating Rating - total i64 -} - -struct JsonPage { - items []PricedItem +struct Page { + items []Item count int } fn main() { - raw := os.read_file('/data/dataset.json') or { '[]' } - dataset := json.decode([]Item, raw) or { []Item{} } - + dataset := json.decode([]Item, os.read_file('/data/dataset.json') or { '[]' }) or { []Item{} } mut app := viltrum.new() - app.server_options(viltrum.ServerOptions{ - handle_signals: false - send_date: true - server_header: 'viltrum' - epoll_cores: runtime.nr_cpus() - }) - + // One copy of the dataset per worker: totals are written per request, and a + // worker only touches its own copy. + mut copies := [][]Item{} + for _ in 0 .. app.worker_count() { + copies << dataset.clone() + } app.get('/baseline11', baseline) app.post('/baseline11', baseline) - app.get('/pipeline', fn (_ viltrum.Request) viltrum.Response { - return viltrum.text(200, 'ok') + app.get('/pipeline', fn (mut c viltrum.Ctx) { + c.text(200, 'ok') }) - app.get('/json/:count', viltrum.chain([viltrum.gzip], fn [dataset] (req viltrum.Request) viltrum.Response { - mut n := (req.param('count') or { '0' }).int() + app.get('/json/:count', viltrum.gzip, fn [mut copies] (mut c viltrum.Ctx) { + mut items := copies[c.worker_id()] + mut n := int(c.param_int('count') or { 0 }) if n < 0 { n = 0 - } else if n > dataset.len { - n = dataset.len + } else if n > items.len { + n = items.len } - m := i64((req.query_param('m') or { '1' }).int()) - mut items := []PricedItem{cap: n} - for it in dataset[..n] { - items << PricedItem{ - id: it.id - name: it.name - category: it.category - price: it.price - quantity: it.quantity - active: it.active - tags: it.tags - rating: it.rating - total: i64(it.price) * i64(it.quantity) * m - } + m := c.query_int('m') or { 1 } + for i in 0 .. n { + items[i].total = i64(items[i].price) * i64(items[i].quantity) * m } - return viltrum.json(200, json.encode(JsonPage{ items: items, count: n })) - })) - app.ws('/ws', fn (mut s viltrum.WsSocket) { - for { - msg := s.read_message() or { break } - if msg.is_text() { - s.write_text(msg.text()) or { break } - } else if msg.is_binary() { - s.write_binary(msg.data) or { break } - } + mut page := unsafe { items } + unsafe { + // n is clamped to items.len and jsonenc only reads page, so the shortened view is safe + page.len = n } - s.close_quiet() + c.json(200, Page{ + items: page + count: n + }) }) app.listen('0.0.0.0:8080') or { panic(err) } } -fn baseline(req viltrum.Request) viltrum.Response { - mut sum := i64((req.query_param('a') or { '0' }).int()) + - (req.query_param('b') or { '0' }).int() - if req.body.len > 0 { - sum += req.text().trim_space().i64() - } - return viltrum.text(200, sum.str()) +fn baseline(mut c viltrum.Ctx) { + sum := (c.query_int('a') or { 0 }) + (c.query_int('b') or { 0 }) + (c.body_int() or { 0 }) + c.text_int(200, sum) } diff --git a/frameworks/viltrum/meta.json b/frameworks/viltrum/meta.json index 80fe423cf2..5e8840d030 100644 --- a/frameworks/viltrum/meta.json +++ b/frameworks/viltrum/meta.json @@ -3,11 +3,11 @@ "language": "V", "engine": "viltrum", "type": "emerging", - "mode": "tuned", + "mode": "standard", "completeness": { "routing": true, "middleware": true, "request": true, "response": true }, - "description": "HTTP framework for V with its own HTTP/1.1 engine: router with :params, middleware chain, first-party WebSocket, strict request framing (Content-Length and chunked, smuggling-hardened, fuzzed in CI). Tuned: one SO_REUSEPORT epoll loop per CPU (epoll_cores) instead of the default thread-per-connection I/O. json-comp uses viltrum's gzip middleware.", + "description": "HTTP framework for V with its own HTTP/1.1 engine: one SO_REUSEPORT epoll worker per CPU, pipelined responses batched into one write, and no allocation per request inside the framework (CI-enforced). Router with :params, middleware chain (c.next()), compile-time JSON encoder, gzip middleware on miniz. Request framing is smuggling-hardened (Content-Length and chunked) and fuzzed in CI. Default configuration.", "repo": "https://github.com/Tuntii/viltrum", "enabled": true, - "tests": ["baseline", "pipelined", "limited-conn", "json-comp", "echo-ws"], + "tests": ["baseline", "pipelined", "limited-conn", "json-comp"], "maintainers": ["Tuntii"] } From 5bb933d20392233f2fb8eb1f259519181cab3815 Mon Sep 17 00:00:00 2001 From: Tunay <121901995+Tuntii@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:01:35 +0300 Subject: [PATCH 2/8] viltrum: 1.0.1 (GC scope pins off the hot path) --- frameworks/viltrum/Dockerfile | 2 +- frameworks/viltrum/main.v | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/frameworks/viltrum/Dockerfile b/frameworks/viltrum/Dockerfile index 546df9c5ee..6202b35b94 100644 --- a/frameworks/viltrum/Dockerfile +++ b/frameworks/viltrum/Dockerfile @@ -9,7 +9,7 @@ RUN curl -fsSL -o /tmp/v.zip https://github.com/vlang/v/releases/download/0.5.2/ unzip -q /tmp/v.zip -d /opt && rm /tmp/v.zip ENV PATH=/opt/v:$PATH -ARG VILTRUM_REF=v1.0.0 +ARG VILTRUM_REF=v1.0.1 RUN git clone --depth 1 --branch ${VILTRUM_REF} https://github.com/Tuntii/viltrum /root/.vmodules/viltrum WORKDIR /app diff --git a/frameworks/viltrum/main.v b/frameworks/viltrum/main.v index 26354454df..3ad2506b84 100644 --- a/frameworks/viltrum/main.v +++ b/frameworks/viltrum/main.v @@ -41,31 +41,37 @@ fn main() { app.get('/pipeline', fn (mut c viltrum.Ctx) { c.text(200, 'ok') }) + // Only scalars are local here: V -prod pins every local struct or array around each + // call statement (see docs/request-response.md), so the dataset stays captured. app.get('/json/:count', viltrum.gzip, fn [mut copies] (mut c viltrum.Ctx) { - mut items := copies[c.worker_id()] + w := c.worker_id() mut n := int(c.param_int('count') or { 0 }) if n < 0 { n = 0 - } else if n > items.len { - n = items.len + } else if n > copies[w].len { + n = copies[w].len } m := c.query_int('m') or { 1 } for i in 0 .. n { - items[i].total = i64(items[i].price) * i64(items[i].quantity) * m - } - mut page := unsafe { items } - unsafe { - // n is clamped to items.len and jsonenc only reads page, so the shortened view is safe - page.len = n + copies[w][i].total = i64(copies[w][i].price) * i64(copies[w][i].quantity) * m } c.json(200, Page{ - items: page + items: first(copies[w], n) count: n }) }) app.listen('0.0.0.0:8080') or { panic(err) } } +// first is a view of a's first n items; safe because n <= a.len and jsonenc only reads. +fn first(a []Item, n int) []Item { + mut v := unsafe { a } + unsafe { + v.len = n + } + return v +} + fn baseline(mut c viltrum.Ctx) { sum := (c.query_int('a') or { 0 }) + (c.query_int('b') or { 0 }) + (c.body_int() or { 0 }) c.text_int(200, sum) From b58720cbfb3f05fe0148ed358a1d4d6ad13d5a90 Mon Sep 17 00:00:00 2001 From: Tunay <121901995+Tuntii@users.noreply.github.com> Date: Fri, 16 Oct 2026 17:31:16 +0300 Subject: [PATCH 3/8] viltrum: subscribe to latency-1m, latency-10k, latency-500k-8cpu (same /baseline11 endpoint) --- frameworks/viltrum/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/viltrum/meta.json b/frameworks/viltrum/meta.json index 5e8840d030..4c49453d2c 100644 --- a/frameworks/viltrum/meta.json +++ b/frameworks/viltrum/meta.json @@ -8,6 +8,6 @@ "description": "HTTP framework for V with its own HTTP/1.1 engine: one SO_REUSEPORT epoll worker per CPU, pipelined responses batched into one write, and no allocation per request inside the framework (CI-enforced). Router with :params, middleware chain (c.next()), compile-time JSON encoder, gzip middleware on miniz. Request framing is smuggling-hardened (Content-Length and chunked) and fuzzed in CI. Default configuration.", "repo": "https://github.com/Tuntii/viltrum", "enabled": true, - "tests": ["baseline", "pipelined", "limited-conn", "json-comp"], + "tests": ["baseline", "pipelined", "limited-conn", "json-comp", "latency-1m", "latency-10k", "latency-500k-8cpu"], "maintainers": ["Tuntii"] } From 0a636f29056f4bec146bf4f1df3ebcc2d4f260bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Oct 2026 16:23:09 +0000 Subject: [PATCH 4/8] Benchmark results: viltrum [skip ci] --- site/data/frameworks.json | 4 +- site/data/results/viltrum.json | 156 +++++++++++++----- site/static/logs/baseline/4096/viltrum.log | 3 +- site/static/logs/json-comp/16384/viltrum.log | 3 +- site/static/logs/json-comp/4096/viltrum.log | 3 +- site/static/logs/latency-10k/1024/viltrum.log | 1 + site/static/logs/latency-1m/1024/viltrum.log | 1 + .../logs/latency-500k-8cpu/1024/viltrum.log | 1 + .../static/logs/limited-conn/4096/viltrum.log | 3 +- site/static/logs/pipelined/4096/viltrum.log | 3 +- 10 files changed, 124 insertions(+), 54 deletions(-) create mode 100644 site/static/logs/latency-10k/1024/viltrum.log create mode 100644 site/static/logs/latency-1m/1024/viltrum.log create mode 100644 site/static/logs/latency-500k-8cpu/1024/viltrum.log diff --git a/site/data/frameworks.json b/site/data/frameworks.json index e42b62f88e..663accda9d 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -2420,11 +2420,11 @@ }, "viltrum": { "dir": "viltrum", - "description": "HTTP framework for V with its own HTTP/1.1 engine: router with :params, middleware chain, first-party WebSocket, strict request framing (Content-Length and chunked, smuggling-hardened, fuzzed in CI). Tuned: one SO_REUSEPORT epoll loop per CPU (epoll_cores) instead of the default thread-per-connection I/O. json-comp uses viltrum's gzip middleware.", + "description": "HTTP framework for V with its own HTTP/1.1 engine: one SO_REUSEPORT epoll worker per CPU, pipelined responses batched into one write, and no allocation per request inside the framework (CI-enforced). Router with :params, middleware chain (c.next()), compile-time JSON encoder, gzip middleware on miniz. Request framing is smuggling-hardened (Content-Length and chunked) and fuzzed in CI. Default configuration.", "repo": "https://github.com/Tuntii/viltrum", "type": "emerging", "engine": "viltrum", - "mode": "tuned", + "mode": "standard", "completeness": { "routing": true, "middleware": true, diff --git a/site/data/results/viltrum.json b/site/data/results/viltrum.json index e75c8ae88b..46d5f149ae 100644 --- a/site/data/results/viltrum.json +++ b/site/data/results/viltrum.json @@ -4,19 +4,19 @@ "baseline-4096": { "framework": "viltrum", "language": "V", - "rps": 585594, - "avg_latency": "7.03ms", - "p99_latency": "17.70ms", - "cpu": "1871.9%", - "memory": "124MiB", + "rps": 3863553, + "avg_latency": "1.06ms", + "p99_latency": "2.24ms", + "cpu": "6515.5%", + "memory": "326MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "88.78MB/s", - "input_bw": "45.24MB/s", + "bandwidth": "434.66MB/s", + "input_bw": "298.45MB/s", "reconnects": 0, - "status_2xx": 2927973, + "status_2xx": 19317765, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -81,19 +81,19 @@ "json-comp-16384": { "framework": "viltrum", "language": "V", - "rps": 17366, - "avg_latency": "847.75ms", - "p99_latency": "1.16s", - "cpu": "2398.3%", - "memory": "481MiB", + "rps": 190693, + "avg_latency": "84.67ms", + "p99_latency": "609.00ms", + "cpu": "2412.7%", + "memory": "1.6GiB", "connections": 16384, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "29.61MB/s", - "input_bw": "1.29MB/s", - "reconnects": 0, - "status_2xx": 86834, + "bandwidth": "252.50MB/s", + "input_bw": "14.19MB/s", + "reconnects": 31765, + "status_2xx": 953468, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -101,39 +101,111 @@ "json-comp-4096": { "framework": "viltrum", "language": "V", - "rps": 14418, - "avg_latency": "276.28ms", - "p99_latency": "454.20ms", - "cpu": "2249.2%", - "memory": "177MiB", + "rps": 238513, + "avg_latency": "17.16ms", + "p99_latency": "59.20ms", + "cpu": "4290.7%", + "memory": "1.5GiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "24.59MB/s", - "input_bw": "1.07MB/s", - "reconnects": 345, - "status_2xx": 72091, + "bandwidth": "315.68MB/s", + "input_bw": "17.74MB/s", + "reconnects": 45931, + "status_2xx": 1192566, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 }, + "latency-10k-1024": { + "framework": "viltrum", + "language": "V", + "rps": 9983, + "avg_latency": "67.9us", + "p99_latency": "99.0us", + "cpu": "22.3%", + "memory": "133MiB", + "connections": 1024, + "threads": 64, + "duration": "20s", + "pipeline": 1, + "bandwidth": "1.17MB/s", + "reconnects": 0, + "status_2xx": 199682, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0, + "cpu_usec": 4629393, + "cpu_per_req_us": 23.1838, + "target_rate": 10000, + "rate_ratio": 0.9983, + "p99_9_latency": "140.0us" + }, + "latency-1m-1024": { + "framework": "viltrum", + "language": "V", + "rps": 997933, + "avg_latency": "107.4us", + "p99_latency": "164.0us", + "cpu": "2267.6%", + "memory": "104MiB", + "connections": 1024, + "threads": 64, + "duration": "20s", + "pipeline": 1, + "bandwidth": "116.76MB/s", + "reconnects": 0, + "status_2xx": 19962406, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0, + "cpu_usec": 413910948, + "cpu_per_req_us": 20.7345, + "target_rate": 1000000, + "rate_ratio": 0.9979, + "p99_9_latency": "8964.0us" + }, + "latency-500k-8cpu-1024": { + "framework": "viltrum", + "language": "V", + "rps": 497831, + "avg_latency": "16298.9us", + "p99_latency": "247488.0us", + "cpu": "810.6%", + "memory": "83MiB", + "connections": 1024, + "threads": 64, + "duration": "20s", + "pipeline": 1, + "bandwidth": "58.25MB/s", + "reconnects": 0, + "status_2xx": 9960146, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0, + "cpu_usec": 155024423, + "cpu_per_req_us": 15.5645, + "target_rate": 500000, + "rate_ratio": 0.9957, + "p99_9_latency": "301440.0us" + }, "limited-conn-4096": { "framework": "viltrum", "language": "V", - "rps": 538675, - "avg_latency": "7.58ms", - "p99_latency": "30.20ms", - "cpu": "2267.3%", - "memory": "131MiB", + "rps": 157991, + "avg_latency": "24.66ms", + "p99_latency": "444.80ms", + "cpu": "373.0%", + "memory": "2.2GiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "81.66MB/s", - "input_bw": "41.61MB/s", - "reconnects": 267765, - "status_2xx": 2693376, + "bandwidth": "17.77MB/s", + "input_bw": "12.20MB/s", + "reconnects": 78534, + "status_2xx": 789957, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -141,18 +213,18 @@ "pipelined-4096": { "framework": "viltrum", "language": "V", - "rps": 878128, - "avg_latency": "74.40ms", - "p99_latency": "116.40ms", - "cpu": "1291.7%", - "memory": "150MiB", + "rps": 25297584, + "avg_latency": "2.60ms", + "p99_latency": "4.33ms", + "cpu": "6768.7%", + "memory": "320MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 16, - "bandwidth": "133.10MB/s", + "bandwidth": "2.78GB/s", "reconnects": 0, - "status_2xx": 4390640, + "status_2xx": 126487920, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 diff --git a/site/static/logs/baseline/4096/viltrum.log b/site/static/logs/baseline/4096/viltrum.log index 9b2ffcb900..4c36bd0d6d 100644 --- a/site/static/logs/baseline/4096/viltrum.log +++ b/site/static/logs/baseline/4096/viltrum.log @@ -1,2 +1 @@ -[viltrum] epoll: upgrade/WS is handed off its own thread -[viltrum] listening on http://0.0.0.0:8080 (epoll_cores=128) +[viltrum] listening on http://0.0.0.0:8080 (64 workers) diff --git a/site/static/logs/json-comp/16384/viltrum.log b/site/static/logs/json-comp/16384/viltrum.log index 9b2ffcb900..4c36bd0d6d 100644 --- a/site/static/logs/json-comp/16384/viltrum.log +++ b/site/static/logs/json-comp/16384/viltrum.log @@ -1,2 +1 @@ -[viltrum] epoll: upgrade/WS is handed off its own thread -[viltrum] listening on http://0.0.0.0:8080 (epoll_cores=128) +[viltrum] listening on http://0.0.0.0:8080 (64 workers) diff --git a/site/static/logs/json-comp/4096/viltrum.log b/site/static/logs/json-comp/4096/viltrum.log index 9b2ffcb900..4c36bd0d6d 100644 --- a/site/static/logs/json-comp/4096/viltrum.log +++ b/site/static/logs/json-comp/4096/viltrum.log @@ -1,2 +1 @@ -[viltrum] epoll: upgrade/WS is handed off its own thread -[viltrum] listening on http://0.0.0.0:8080 (epoll_cores=128) +[viltrum] listening on http://0.0.0.0:8080 (64 workers) diff --git a/site/static/logs/latency-10k/1024/viltrum.log b/site/static/logs/latency-10k/1024/viltrum.log new file mode 100644 index 0000000000..4c36bd0d6d --- /dev/null +++ b/site/static/logs/latency-10k/1024/viltrum.log @@ -0,0 +1 @@ +[viltrum] listening on http://0.0.0.0:8080 (64 workers) diff --git a/site/static/logs/latency-1m/1024/viltrum.log b/site/static/logs/latency-1m/1024/viltrum.log new file mode 100644 index 0000000000..4c36bd0d6d --- /dev/null +++ b/site/static/logs/latency-1m/1024/viltrum.log @@ -0,0 +1 @@ +[viltrum] listening on http://0.0.0.0:8080 (64 workers) diff --git a/site/static/logs/latency-500k-8cpu/1024/viltrum.log b/site/static/logs/latency-500k-8cpu/1024/viltrum.log new file mode 100644 index 0000000000..71dfc8f547 --- /dev/null +++ b/site/static/logs/latency-500k-8cpu/1024/viltrum.log @@ -0,0 +1 @@ +[viltrum] listening on http://0.0.0.0:8080 (8 workers) diff --git a/site/static/logs/limited-conn/4096/viltrum.log b/site/static/logs/limited-conn/4096/viltrum.log index 9b2ffcb900..4c36bd0d6d 100644 --- a/site/static/logs/limited-conn/4096/viltrum.log +++ b/site/static/logs/limited-conn/4096/viltrum.log @@ -1,2 +1 @@ -[viltrum] epoll: upgrade/WS is handed off its own thread -[viltrum] listening on http://0.0.0.0:8080 (epoll_cores=128) +[viltrum] listening on http://0.0.0.0:8080 (64 workers) diff --git a/site/static/logs/pipelined/4096/viltrum.log b/site/static/logs/pipelined/4096/viltrum.log index 9b2ffcb900..4c36bd0d6d 100644 --- a/site/static/logs/pipelined/4096/viltrum.log +++ b/site/static/logs/pipelined/4096/viltrum.log @@ -1,2 +1 @@ -[viltrum] epoll: upgrade/WS is handed off its own thread -[viltrum] listening on http://0.0.0.0:8080 (epoll_cores=128) +[viltrum] listening on http://0.0.0.0:8080 (64 workers) From 0c097f4139c2ca7ce7bcf87cfb411dc8872787c4 Mon Sep 17 00:00:00 2001 From: Tunay <121901995+Tuntii@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:18:41 +0300 Subject: [PATCH 5/8] viltrum: type experimental --- frameworks/viltrum/README.md | 3 +++ frameworks/viltrum/meta.json | 2 +- site/data/frameworks.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frameworks/viltrum/README.md b/frameworks/viltrum/README.md index 0800822e06..c82d01d2c7 100644 --- a/frameworks/viltrum/README.md +++ b/frameworks/viltrum/README.md @@ -10,3 +10,6 @@ This entry runs viltrum with its default configuration (standard mode). | baseline, limited-conn | `GET/POST /baseline11` | `c.query_int`, `c.body_int` (Content-Length or chunked) | | pipelined | `GET /pipeline` | the engine batches pipelined responses into one write | | json-comp | `GET /json/:count?m=` | `c.json` per request, `viltrum.gzip` middleware | + +The entry is typed `experimental`: viltrum's first commit is from July 2026, so it is new work +that has not proved itself in production yet, which is what that tier is for. diff --git a/frameworks/viltrum/meta.json b/frameworks/viltrum/meta.json index 4c49453d2c..572472e9ac 100644 --- a/frameworks/viltrum/meta.json +++ b/frameworks/viltrum/meta.json @@ -2,7 +2,7 @@ "display_name": "viltrum", "language": "V", "engine": "viltrum", - "type": "emerging", + "type": "experimental", "mode": "standard", "completeness": { "routing": true, "middleware": true, "request": true, "response": true }, "description": "HTTP framework for V with its own HTTP/1.1 engine: one SO_REUSEPORT epoll worker per CPU, pipelined responses batched into one write, and no allocation per request inside the framework (CI-enforced). Router with :params, middleware chain (c.next()), compile-time JSON encoder, gzip middleware on miniz. Request framing is smuggling-hardened (Content-Length and chunked) and fuzzed in CI. Default configuration.", diff --git a/site/data/frameworks.json b/site/data/frameworks.json index 663accda9d..8de6f02778 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -2422,7 +2422,7 @@ "dir": "viltrum", "description": "HTTP framework for V with its own HTTP/1.1 engine: one SO_REUSEPORT epoll worker per CPU, pipelined responses batched into one write, and no allocation per request inside the framework (CI-enforced). Router with :params, middleware chain (c.next()), compile-time JSON encoder, gzip middleware on miniz. Request framing is smuggling-hardened (Content-Length and chunked) and fuzzed in CI. Default configuration.", "repo": "https://github.com/Tuntii/viltrum", - "type": "emerging", + "type": "experimental", "engine": "viltrum", "mode": "standard", "completeness": { From a44516a3faae4619ad9684f7fb279e0d861b6cc8 Mon Sep 17 00:00:00 2001 From: Tunay <121901995+Tuntii@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:28:48 +0300 Subject: [PATCH 6/8] viltrum: 1.1.0, async profile (GET /delay/{ms} answered from per-worker timers) --- frameworks/viltrum/Dockerfile | 2 +- frameworks/viltrum/main.v | 13 +++++++++++++ frameworks/viltrum/meta.json | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/frameworks/viltrum/Dockerfile b/frameworks/viltrum/Dockerfile index 6202b35b94..aee2b1eceb 100644 --- a/frameworks/viltrum/Dockerfile +++ b/frameworks/viltrum/Dockerfile @@ -9,7 +9,7 @@ RUN curl -fsSL -o /tmp/v.zip https://github.com/vlang/v/releases/download/0.5.2/ unzip -q /tmp/v.zip -d /opt && rm /tmp/v.zip ENV PATH=/opt/v:$PATH -ARG VILTRUM_REF=v1.0.1 +ARG VILTRUM_REF=v1.1.0 RUN git clone --depth 1 --branch ${VILTRUM_REF} https://github.com/Tuntii/viltrum /root/.vmodules/viltrum WORKDIR /app diff --git a/frameworks/viltrum/main.v b/frameworks/viltrum/main.v index 3ad2506b84..2f4f83bd4c 100644 --- a/frameworks/viltrum/main.v +++ b/frameworks/viltrum/main.v @@ -60,6 +60,15 @@ fn main() { count: n }) }) + // The worker is not held while a request waits: after() answers it from a timer. + app.get('/delay/:ms', fn (mut c viltrum.Ctx) { + ms := c.param_int('ms') or { -1 } + if ms < 0 { + c.text(400, 'bad delay') + return + } + c.after(int(ms), delay_done, u64(ms)) + }) app.listen('0.0.0.0:8080') or { panic(err) } } @@ -72,6 +81,10 @@ fn first(a []Item, n int) []Item { return v } +fn delay_done(mut c viltrum.Ctx, ms u64) { + c.text_int(200, i64(ms)) +} + fn baseline(mut c viltrum.Ctx) { sum := (c.query_int('a') or { 0 }) + (c.query_int('b') or { 0 }) + (c.body_int() or { 0 }) c.text_int(200, sum) diff --git a/frameworks/viltrum/meta.json b/frameworks/viltrum/meta.json index 572472e9ac..280c1e80d4 100644 --- a/frameworks/viltrum/meta.json +++ b/frameworks/viltrum/meta.json @@ -8,6 +8,6 @@ "description": "HTTP framework for V with its own HTTP/1.1 engine: one SO_REUSEPORT epoll worker per CPU, pipelined responses batched into one write, and no allocation per request inside the framework (CI-enforced). Router with :params, middleware chain (c.next()), compile-time JSON encoder, gzip middleware on miniz. Request framing is smuggling-hardened (Content-Length and chunked) and fuzzed in CI. Default configuration.", "repo": "https://github.com/Tuntii/viltrum", "enabled": true, - "tests": ["baseline", "pipelined", "limited-conn", "json-comp", "latency-1m", "latency-10k", "latency-500k-8cpu"], + "tests": ["baseline", "pipelined", "limited-conn", "json-comp", "latency-1m", "latency-10k", "latency-500k-8cpu", "async"], "maintainers": ["Tuntii"] } From e8269ec62527d154d538cc9a0a9a4d53c1c57934 Mon Sep 17 00:00:00 2001 From: Tunay <121901995+Tuntii@users.noreply.github.com> Date: Mon, 19 Oct 2026 20:12:47 +0300 Subject: [PATCH 7/8] viltrum: document the async profile --- frameworks/viltrum/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/frameworks/viltrum/README.md b/frameworks/viltrum/README.md index c82d01d2c7..bad8668b2c 100644 --- a/frameworks/viltrum/README.md +++ b/frameworks/viltrum/README.md @@ -10,6 +10,7 @@ This entry runs viltrum with its default configuration (standard mode). | baseline, limited-conn | `GET/POST /baseline11` | `c.query_int`, `c.body_int` (Content-Length or chunked) | | pipelined | `GET /pipeline` | the engine batches pipelined responses into one write | | json-comp | `GET /json/:count?m=` | `c.json` per request, `viltrum.gzip` middleware | +| async | `GET /delay/:ms` | `c.after` answers from a per-worker timer; the worker is not held while the request waits | The entry is typed `experimental`: viltrum's first commit is from July 2026, so it is new work that has not proved itself in production yet, which is what that tier is for. From 5afd675bd4f26c3ed89db0fd207bafc48e5db51f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Oct 2026 20:16:02 +0000 Subject: [PATCH 8/8] Benchmark results: viltrum [skip ci] --- site/data/results/viltrum.json | 168 +++++++++++++---------- site/static/logs/async/32000/viltrum.log | 1 + 2 files changed, 95 insertions(+), 74 deletions(-) create mode 100644 site/static/logs/async/32000/viltrum.log diff --git a/site/data/results/viltrum.json b/site/data/results/viltrum.json index 46d5f149ae..76a879b745 100644 --- a/site/data/results/viltrum.json +++ b/site/data/results/viltrum.json @@ -1,22 +1,42 @@ { "framework": "viltrum", "results": { + "async-32000": { + "framework": "viltrum", + "language": "V", + "rps": 2482163, + "avg_latency": "12.79ms", + "p99_latency": "15.40ms", + "cpu": "4478.6%", + "memory": "1.2GiB", + "connections": 32000, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "279.28MB/s", + "input_bw": "113.62MB/s", + "reconnects": 0, + "status_2xx": 24821637, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, "baseline-4096": { "framework": "viltrum", "language": "V", - "rps": 3863553, - "avg_latency": "1.06ms", - "p99_latency": "2.24ms", - "cpu": "6515.5%", - "memory": "326MiB", + "rps": 3940680, + "avg_latency": "1.04ms", + "p99_latency": "2.19ms", + "cpu": "6270.3%", + "memory": "211MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "434.66MB/s", - "input_bw": "298.45MB/s", + "bandwidth": "443.36MB/s", + "input_bw": "304.41MB/s", "reconnects": 0, - "status_2xx": 19317765, + "status_2xx": 19703400, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -81,19 +101,19 @@ "json-comp-16384": { "framework": "viltrum", "language": "V", - "rps": 190693, - "avg_latency": "84.67ms", - "p99_latency": "609.00ms", - "cpu": "2412.7%", - "memory": "1.6GiB", + "rps": 335612, + "avg_latency": "48.38ms", + "p99_latency": "114.60ms", + "cpu": "6406.9%", + "memory": "976MiB", "connections": 16384, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "252.50MB/s", - "input_bw": "14.19MB/s", - "reconnects": 31765, - "status_2xx": 953468, + "bandwidth": "444.19MB/s", + "input_bw": "24.97MB/s", + "reconnects": 60589, + "status_2xx": 1678063, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -101,19 +121,19 @@ "json-comp-4096": { "framework": "viltrum", "language": "V", - "rps": 238513, - "avg_latency": "17.16ms", - "p99_latency": "59.20ms", - "cpu": "4290.7%", - "memory": "1.5GiB", + "rps": 341066, + "avg_latency": "11.99ms", + "p99_latency": "35.00ms", + "cpu": "6259.2%", + "memory": "347MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "315.68MB/s", - "input_bw": "17.74MB/s", - "reconnects": 45931, - "status_2xx": 1192566, + "bandwidth": "451.48MB/s", + "input_bw": "25.37MB/s", + "reconnects": 66721, + "status_2xx": 1705333, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -121,91 +141,91 @@ "latency-10k-1024": { "framework": "viltrum", "language": "V", - "rps": 9983, - "avg_latency": "67.9us", - "p99_latency": "99.0us", - "cpu": "22.3%", - "memory": "133MiB", + "rps": 9980, + "avg_latency": "70.5us", + "p99_latency": "102.0us", + "cpu": "23.2%", + "memory": "75MiB", "connections": 1024, "threads": 64, "duration": "20s", "pipeline": 1, "bandwidth": "1.17MB/s", "reconnects": 0, - "status_2xx": 199682, + "status_2xx": 199627, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0, - "cpu_usec": 4629393, - "cpu_per_req_us": 23.1838, + "cpu_usec": 4844148, + "cpu_per_req_us": 24.266, "target_rate": 10000, - "rate_ratio": 0.9983, - "p99_9_latency": "140.0us" + "rate_ratio": 0.998, + "p99_9_latency": "141.0us" }, "latency-1m-1024": { "framework": "viltrum", "language": "V", - "rps": 997933, - "avg_latency": "107.4us", - "p99_latency": "164.0us", - "cpu": "2267.6%", - "memory": "104MiB", + "rps": 998346, + "avg_latency": "93.8us", + "p99_latency": "166.0us", + "cpu": "2260.3%", + "memory": "76MiB", "connections": 1024, "threads": 64, "duration": "20s", "pipeline": 1, - "bandwidth": "116.76MB/s", + "bandwidth": "116.81MB/s", "reconnects": 0, - "status_2xx": 19962406, + "status_2xx": 19970563, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0, - "cpu_usec": 413910948, - "cpu_per_req_us": 20.7345, + "cpu_usec": 412294705, + "cpu_per_req_us": 20.6451, "target_rate": 1000000, - "rate_ratio": 0.9979, - "p99_9_latency": "8964.0us" + "rate_ratio": 0.9983, + "p99_9_latency": "3317.0us" }, "latency-500k-8cpu-1024": { "framework": "viltrum", "language": "V", - "rps": 497831, - "avg_latency": "16298.9us", - "p99_latency": "247488.0us", - "cpu": "810.6%", - "memory": "83MiB", + "rps": 496317, + "avg_latency": "55250.3us", + "p99_latency": "541952.0us", + "cpu": "792.5%", + "memory": "56MiB", "connections": 1024, "threads": 64, "duration": "20s", "pipeline": 1, - "bandwidth": "58.25MB/s", + "bandwidth": "58.07MB/s", "reconnects": 0, - "status_2xx": 9960146, + "status_2xx": 9930067, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0, - "cpu_usec": 155024423, - "cpu_per_req_us": 15.5645, + "cpu_usec": 153203317, + "cpu_per_req_us": 15.4282, "target_rate": 500000, - "rate_ratio": 0.9957, - "p99_9_latency": "301440.0us" + "rate_ratio": 0.9926, + "p99_9_latency": "611584.0us" }, "limited-conn-4096": { "framework": "viltrum", "language": "V", - "rps": 157991, - "avg_latency": "24.66ms", - "p99_latency": "444.80ms", - "cpu": "373.0%", - "memory": "2.2GiB", + "rps": 2555570, + "avg_latency": "1.46ms", + "p99_latency": "2.25ms", + "cpu": "5905.8%", + "memory": "499MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 1, - "bandwidth": "17.77MB/s", - "input_bw": "12.20MB/s", - "reconnects": 78534, - "status_2xx": 789957, + "bandwidth": "287.34MB/s", + "input_bw": "197.41MB/s", + "reconnects": 1277510, + "status_2xx": 12777852, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 @@ -213,18 +233,18 @@ "pipelined-4096": { "framework": "viltrum", "language": "V", - "rps": 25297584, - "avg_latency": "2.60ms", - "p99_latency": "4.33ms", - "cpu": "6768.7%", - "memory": "320MiB", + "rps": 25879708, + "avg_latency": "2.53ms", + "p99_latency": "3.38ms", + "cpu": "6781.9%", + "memory": "204MiB", "connections": 4096, "threads": 64, "duration": "5s", "pipeline": 16, - "bandwidth": "2.78GB/s", + "bandwidth": "2.84GB/s", "reconnects": 0, - "status_2xx": 126487920, + "status_2xx": 129398544, "status_3xx": 0, "status_4xx": 0, "status_5xx": 0 diff --git a/site/static/logs/async/32000/viltrum.log b/site/static/logs/async/32000/viltrum.log new file mode 100644 index 0000000000..4c36bd0d6d --- /dev/null +++ b/site/static/logs/async/32000/viltrum.log @@ -0,0 +1 @@ +[viltrum] listening on http://0.0.0.0:8080 (64 workers)