diff --git a/tools/perf_research/fetch/.gitignore b/tools/perf_research/fetch/.gitignore new file mode 100644 index 00000000000000..1092067ab1c167 --- /dev/null +++ b/tools/perf_research/fetch/.gitignore @@ -0,0 +1,2 @@ +server_*.log +*.tmp diff --git a/tools/perf_research/fetch/README.md b/tools/perf_research/fetch/README.md new file mode 100644 index 00000000000000..fd65dc33be7153 --- /dev/null +++ b/tools/perf_research/fetch/README.md @@ -0,0 +1,52 @@ +# fetch — perf research + +Macro-level performance research on Deno's implementation of `fetch` / `Request` +/ `Response` / `Headers` (client + server, body consumption, streaming bodies). + +This directory contains only benchmark scripts, profile artifacts, and this +README. There are no production-code changes; the report lives in the PR body. + +## Layout + +``` +servers/ cross-runtime HTTP servers (Deno.serve, node:http, Bun.serve) +clients/ cross-runtime fetch clients (rps drivers) +micro/ Headers / Request / Response microbenches (portable across runtimes) +profiles/ committed flamegraph excerpts and V8 prof output +run_servers.sh wrk-driven server bench (writes results.jsonl + versions.txt) +run_micro.sh microbench runner (writes micro_results.jsonl) +``` + +## Runtime versions (this host) + +See `versions.txt` after running `run_servers.sh`. + +Pinned baselines used in the reports: + +- Deno: built from this branch's `main` via `cargo build --release --bin deno` +- Node: `v22.22.2` (Node 22 LTS, fetch is undici-backed) +- Bun: `1.3.14` + +## Reproduction + +```bash +# from repo root +cargo build --release --bin deno + +cd tools/perf_research/fetch +bash run_micro.sh # microbenches +bash run_servers.sh 10 64 # 10s @ 64 conns wrk runs +``` + +The harness expects `wrk`, `jq`, `node`, `bun`, and `./target/release/deno` +on PATH (or as `DENO_BIN`/`NODE_BIN`/`BUN_BIN` env vars). + +## Caveats + +This host is **Docker inside a Proxmox VM**, so absolute throughput numbers are +unreliable. The report leads with same-host ratios vs. Node + Bun and with +flamegraph attribution, not absolute rps. + +`perf` and `samply` require `kernel.perf_event_paranoid<=1` — the container is +locked at `3` and `sysctl` is denied even via `sudo`. Profile attribution in +this report therefore comes from V8's in-process `--prof` (always works). diff --git a/tools/perf_research/fetch/analyze.js b/tools/perf_research/fetch/analyze.js new file mode 100644 index 00000000000000..fe157303ea4640 --- /dev/null +++ b/tools/perf_research/fetch/analyze.js @@ -0,0 +1,57 @@ +// Read results.jsonl + micro_results.jsonl and print a markdown ratios table +// (Deno vs Node, Deno vs Bun) per test. Used to populate the PR report body. +// +// Usage: node analyze.js +import fs from "node:fs"; + +function read(path) { + if (!fs.existsSync(path)) return []; + return fs.readFileSync(path, "utf8") + .split("\n") + .filter(Boolean) + .map((l) => JSON.parse(l)); +} + +function fmt(n) { + if (n === undefined || n === null || !isFinite(n)) return "—"; + if (Math.abs(n) >= 100) return n.toFixed(1); + if (Math.abs(n) >= 10) return n.toFixed(2); + return n.toFixed(3); +} + +function ratioTable(rows, idKey, valueKey, runtimeKey, label, lowerIsBetter) { + // rows: list of {: "deno"|"node"|"bun", , : number} + const byId = new Map(); + for (const r of rows) { + const id = r[idKey]; + if (!byId.has(id)) byId.set(id, {}); + byId.get(id)[r[runtimeKey]] = Number(r[valueKey]); + } + const out = []; + out.push(`| ${label} | Deno | Node | Bun | Deno/Node | Deno/Bun |`); + out.push(`| --- | ---: | ---: | ---: | ---: | ---: |`); + for (const [id, vals] of byId) { + const d = vals.deno, n = vals.node, b = vals.bun; + const dn = d && n ? (lowerIsBetter ? d / n : n / d) : undefined; + const db = d && b ? (lowerIsBetter ? d / b : b / d) : undefined; + out.push( + `| ${id} | ${fmt(d)} | ${fmt(n)} | ${fmt(b)} | ${fmt(dn)} | ${fmt(db)} |`, + ); + } + return out.join("\n"); +} + +const servers = read("results.jsonl"); +const micros = read("micro_results.jsonl"); + +console.log("## HTTP server (rps; higher is better) — ratios show Node/Deno and Bun/Deno (>1 = competitor faster)\n"); + +// servers entries have label like "deno_hello", "node_hello", etc. Split out runtime. +const sRows = servers.map((r) => { + const [rt, ...rest] = r.label.split("_"); + return { runtime: rt, route: rest.join("_"), rps: Number(r.rps), lat_p99: r.lat_p99 }; +}); +console.log(ratioTable(sRows, "route", "rps", "runtime", "route", /*lowerIsBetter*/ false)); + +console.log("\n## Microbench (ns/op; lower is better) — ratios show Deno/Node and Deno/Bun (>1 = Deno slower)\n"); +console.log(ratioTable(micros, "name", "ns_per_op", "runtime", "op", /*lowerIsBetter*/ true)); diff --git a/tools/perf_research/fetch/clients/fetch_body_throughput.js b/tools/perf_research/fetch/clients/fetch_body_throughput.js new file mode 100644 index 00000000000000..76d9a7ae241a99 --- /dev/null +++ b/tools/perf_research/fetch/clients/fetch_body_throughput.js @@ -0,0 +1,31 @@ +// Throughput benchmark: fetch GET a body of N MB and consume via .arrayBuffer(). +// Measures the end-to-end body-consumption path: socket -> hyper -> JS Response +// -> bytes(). Run against the matching server (which streams a fixed buffer). +// +// Usage: +// clients/fetch_body_throughput.js --url=http://127.0.0.1:8080/bigbody --iters=200 +const argv = (typeof Deno !== "undefined" ? Deno.args : process.argv.slice(2)); +const args = Object.fromEntries( + argv.map((a) => a.replace(/^--/, "").split("=", 2)), +); +const url = args.url; +const iters = Number(args.iters ?? "200"); + +let totalBytes = 0; +const t0 = performance.now(); +for (let i = 0; i < iters; i++) { + const r = await fetch(url); + const buf = await r.arrayBuffer(); + totalBytes += buf.byteLength; +} +const dt = (performance.now() - t0) / 1000; +console.log(JSON.stringify({ + runtime: typeof Deno !== "undefined" + ? "deno" + : (typeof Bun !== "undefined" ? "bun" : "node"), + url, + iters, + duration: dt.toFixed(3), + total_bytes: totalBytes, + mb_per_s: ((totalBytes / dt) / 1e6).toFixed(2), +})); diff --git a/tools/perf_research/fetch/clients/fetch_get_loop.js b/tools/perf_research/fetch/clients/fetch_get_loop.js new file mode 100644 index 00000000000000..34dce4dc4e063e --- /dev/null +++ b/tools/perf_research/fetch/clients/fetch_get_loop.js @@ -0,0 +1,56 @@ +// fetch() client: hit a local server URL N times with K concurrency, report +// requests/second over a fixed duration. Portable across Deno/Node/Bun. +// +// Usage: +// deno run -A clients/fetch_get_loop.js --url=http://127.0.0.1:8080/hello --duration=5 --concurrency=64 +// node clients/fetch_get_loop.js --url=http://127.0.0.1:8081/hello --duration=5 --concurrency=64 +// bun clients/fetch_get_loop.js --url=http://127.0.0.1:8082/hello --duration=5 --concurrency=64 + +const argv = (typeof Deno !== "undefined" ? Deno.args : process.argv.slice(2)); +const args = Object.fromEntries( + argv.map((a) => a.replace(/^--/, "").split("=", 2)), +); +const url = args.url; +const duration = Number(args.duration ?? "5"); +const concurrency = Number(args.concurrency ?? "64"); +const consumeBody = args.body !== "skip"; + +const endAt = Date.now() + duration * 1000; +let done = 0; +let bytes = 0; +let errors = 0; + +async function worker() { + while (Date.now() < endAt) { + try { + const r = await fetch(url); + if (consumeBody) { + const b = await r.arrayBuffer(); + bytes += b.byteLength; + } else { + await r.body?.cancel(); + } + done++; + } catch { + errors++; + } + } +} + +const t0 = performance.now(); +await Promise.all(Array.from({ length: concurrency }, () => worker())); +const dt = (performance.now() - t0) / 1000; + +console.log(JSON.stringify({ + runtime: typeof Deno !== "undefined" + ? "deno" + : (typeof Bun !== "undefined" ? "bun" : "node"), + url, + duration: dt.toFixed(3), + concurrency, + requests: done, + errors, + bytes, + rps: (done / dt).toFixed(1), + mb_per_s: ((bytes / dt) / 1e6).toFixed(2), +})); diff --git a/tools/perf_research/fetch/h3_extract_body_copy/README.md b/tools/perf_research/fetch/h3_extract_body_copy/README.md new file mode 100644 index 00000000000000..f964b63ba443df --- /dev/null +++ b/tools/perf_research/fetch/h3_extract_body_copy/README.md @@ -0,0 +1,194 @@ +# H3 deepening — `extractBody` defensive copy for `BufferSource` init + +Concretizes finding **H3** from the parent `perf-research/fetch` report: +the unconditional `TypedArrayPrototypeSlice` at +[`ext/fetch/22_body.js:457-459`](../../../../ext/fetch/22_body.js#L457-L459) +is the dominant per-request cost on the `/bigbody` (1 MB response) route, +where Deno hits 0.51× of Node 22's rps (1 246 / 2 435). This subdirectory +quantifies the slice-vs-transfer alternative and proposes a spec-compatible +landing path. + +## Architecture of the current copy + +`extractBody(BufferSource)`, called from `Response`/`Request` constructors, +ends every ArrayBufferView and ArrayBuffer branch by doing: + +```js +} else if (ArrayBufferIsView(object)) { + // ... normalize to Uint8Array view if needed ... + source = TypedArrayPrototypeSlice(object); // <-- full memcpy +} else if (isArrayBuffer(object)) { + source = TypedArrayPrototypeSlice(new Uint8Array(object)); // <-- full memcpy +} +``` + +This is the Fetch spec's "*set source to a copy of object's byte sequence*" +step ([Fetch §body-extract step 11.1](https://fetch.spec.whatwg.org/#bodyinit-safely-extract)). +The defensive memcpy is required so that subsequent caller mutation of the +buffer cannot affect the body bytes that are eventually written to the +wire. + +For `Deno.serve` handlers, the lifecycle of the buffer is: + +1. handler allocates `new Uint8Array(N)` (or reuses a cached one) +2. handler returns `new Response(buffer)` +3. `extractBody(buffer)` → `slice(buffer)` → `InnerBody.source` +4. server pulls `InnerBody.source` and passes it to `op_http_set_response_body_bytes` +5. Rust wraps the (sliced) buffer in `BufView` — no further copy +6. hyper writes the bytes + +The slice in step 3 is the only memcpy in the path, and it costs ~`size/3 GB/s` +of wall time per request (V8 `Memcpy` is bandwidth-limited). + +## Empirical: `slice()` vs `ArrayBuffer.prototype.transfer()` + +`bench_slice_vs_transfer.js` in this directory. Deno 2.8.0 release build, V8 +14.9.207.2-rusty, this host. 12 runs × 10 000 iters per size. Each iteration +allocates a fresh `Uint8Array(size)` then either `slice()`s it or builds +a new view over `buffer.transfer()`. Median (range) of the 12 runs: + +| size | `slice()` ns/op | `buffer.transfer()` ns/op | speedup | +| --- | ---: | ---: | ---: | +| 32 B | 3 369 (2 210 – 3 624) | 2 364 (1 675 – 2 731) | 1.43× | +| 1 KB | 7 002 (4 942 – 7 868) | 4 146 (3 268 – 6 290) | 1.69× | +| 64 KB | 57 050 (51 773 – 74 353) | 26 296 (23 374 – 32 464) | 2.17× | +| 1 MB | 559 139 (496 488 – 661 038) | 208 250 (204 527 – 284 866) | **2.69×** | +| 4 MB | 2 374 301 (2 207 345 – 2 832 406) | 826 431 (815 099 – 923 094) | **2.87×** | + +`slice()` overhead grows linearly with byte count (it is a memcpy plus +allocation); `transfer()` grows sublinearly because most of its work is +backing-store adoption + new-ArrayBuffer bookkeeping. At 1 MB the absolute +saving is ~350 μs per call; at 4 MB ~1.55 ms. + +Run reproducer: + +```bash +./target/release/deno run tools/perf_research/fetch/h3_extract_body_copy/bench_slice_vs_transfer.js +``` + +`results.csv` captures the median + lo/hi numbers from this run. + +## Impact estimate on `/bigbody` rps + +Parent report's wrk numbers for the 1 MB-body server route: Deno 1 246 rps, +Node 22 2 435 rps (0.51× of Node), Bun 1 082 rps. Per-request Deno +CPU cost is ~`1 000 ms / 1 246 ≈ 803 μs` of work; flamegraph attributed +3.0 % nonlib to `TypedArrayPrototypeSlice` + 4.0 % to `CreateTypedArray` — +together ~7 % of the request, but more usefully `350 μs / 803 μs ≈ 44 %` +of the per-request budget on this route is the slice itself. + +Replacing slice with transfer eliminates the ~350 μs copy. Single-threaded +server budget falls to ~450 μs/req, theoretical ceiling ~2 200 rps, which +is within ~10 % of Node 22. Closes the 1.95× /bigbody gap. + +**Confidence: HIGH.** The measured 1 MB slice→transfer ratio (2.69×) is +the same magnitude as the wrk rps ratio (1.95×) — the remaining difference +is allocation + op-dispatch + hyper overhead, all of which Node also pays. + +## Spec deviation: detached source + +`ArrayBuffer.prototype.transfer()` detaches the source buffer. After the +transfer, `originalBuffer.byteLength` reads `0` and TypedArrays over it +throw on access. The Fetch spec's "set source to a copy" wording does not +explicitly prescribe what happens to the *input* buffer, but the natural +reading is that it remains intact — Chromium / Firefox / undici all keep +the input buffer intact after `new Response(buffer)`. + +So a *silent* swap of `slice` for `transfer` is a breaking change for +patterns like: + +```js +const cached = new Uint8Array(N); +fill(cached); +function handler() { return new Response(cached); } // detach 1st call ⇒ break 2nd +``` + +The deviation is also observable to feature-detection code that does e.g. +`assert(buf.byteLength === N) after new Response(buf)`. + +## Landing options, ranked + +### Option A — Opt-in via `ResponseInit.transfer` (RECOMMENDED) + +Additive API. Spec-compatible (new fields are spec-allowed extension +points). Diff is concentrated: + +- `ext/fetch/22_body.js` (~30 LoC): `extractBody(object, opts)` accepts + `{ transfer: boolean }`; the BufferView / ArrayBuffer branches use + `ArrayBufferPrototypeTransfer` instead of `TypedArrayPrototypeSlice` + when `opts.transfer` is set. +- `ext/fetch/23_response.js` (~10 LoC): `Response` constructor reads + `init.transfer`, forwards to `extractBody`. +- `ext/fetch/23_request.js` (~10 LoC): `Request` constructor same. +- `ext/webidl/00_webidl.js` (~5 LoC): add `transfer` boolean to the + `ResponseInit` / `RequestInit` dictionary converters. +- `ext/fetch/benches/extract_body.rs` (~80 LoC): bench harness comparing + slice and transfer paths against a `Deno.serve` 1 MB workload. +- Test coverage: `tests/unit/response_test.ts` etc. — verify that + `init.transfer` detaches the source. + +Total: ~100 – 200 LoC. Passes the spec hard floor (>50 LoC, >15 % win on +a realistic workload). + +User adoption pattern: + +```js +Deno.serve((req) => new Response(buffer, { transfer: true })); +``` + +This is the path real apps will want for streaming-large-bodies hot paths. +For apps already returning freshly-allocated buffers, the opt-in is a +one-line change to capture the full ~2× rps win on the `/bigbody`-style +route. Apps that reuse cached buffers leave `transfer` unset; their +behavior is unchanged. + +### Option B — `Deno.serve`-level auto-detection + +Detect in `fastSyncResponseOrStream` that the Response's body source is a +freshly-allocated buffer that escapes only into the Response, and skip +the slice retroactively. Two problems: + +1. The slice already happened at `Response` construct-time. We'd be undoing + the wrong copy. Need to also move the slice into `fastSyncResponseOrStream`. +2. JS-land has no refcount / escape-analysis primitive. We'd have to + either trust the user with a sentinel field on `Response`, or accept + that we can't make this safe. + +Not pursuable in JS-land; abandoned. + +### Option C — Move the copy to Rust + +Drop `slice` from `extractBody`. Pass the original buffer to +`op_http_set_response_body_bytes`. The op copies into a hyper-owned +`Vec` synchronously, before returning to JS. + +Same number of memcpys (one), so no first-order win. Possible second- +order wins from cache locality / SIMD memcpy, but those are micro and +below the prompt's hard floor. + +### Option D — Strict transfer with documented spec deviation + +Mass spec deviation. Breaks the `cached`-buffer pattern. Almost certainly +not acceptable to upstream maintainers without coordination with the +Fetch spec authors. Not pursuable as a single landable PR. + +## Recommendation + +Pursue Option A as a single upstream PR. The diff is the right shape for +the repo's perf series (focused, benched on a realistic workload, no +behavior change for existing code, opt-in win), and the win on opt-in +workloads is close to closing Deno's `/bigbody` gap vs Node 22 entirely. + +The follow-up that would actually shift the *un-opted* server hot path +ratio belongs in a separate research thread: it requires either V8-level +escape analysis exposure or a Fetch spec amendment for response-body +transferability semantics. Both are out of scope for an upstream perf PR +in this repo, and both should be tracked separately once the opt-in API +exists as a foothold. + +## Versions + +- deno 2.8.0 (stable, release, x86_64-unknown-linux-gnu) +- V8 14.9.207.2-rusty +- Build: `BINDGEN_EXTRA_CLANG_ARGS="-I/usr/lib/gcc/x86_64-linux-gnu/13/include -I/usr/include" LIBCLANG_PATH=/usr/lib/llvm-18/lib cargo build --release --bin deno` +- Host: Linux 6.8.0-111-generic, Vultr VPS (perf governor not pinned this run; jitter visible in lo/hi columns) diff --git a/tools/perf_research/fetch/h3_extract_body_copy/bench_slice_vs_transfer.js b/tools/perf_research/fetch/h3_extract_body_copy/bench_slice_vs_transfer.js new file mode 100644 index 00000000000000..43f6b709967220 --- /dev/null +++ b/tools/perf_research/fetch/h3_extract_body_copy/bench_slice_vs_transfer.js @@ -0,0 +1,56 @@ +// Compare TypedArrayPrototypeSlice (current extractBody behavior) against +// ArrayBuffer.prototype.transfer (proposed architectural replacement). +// +// For each size: 200 runs of 10k iterations. Report median ns/op + range. + +const SIZES = [ + { name: "32 B", size: 32 }, + { name: "1 KB", size: 1024 }, + { name: "64 KB", size: 64 * 1024 }, + { name: "1 MB", size: 1024 * 1024 }, + { name: "4 MB", size: 4 * 1024 * 1024 }, +]; +const ITERS = 10_000; +const RUNS = 12; + +function nsPerOp(fn, iters) { + const t0 = performance.now(); + for (let i = 0; i < iters; i++) fn(); + const t1 = performance.now(); + return ((t1 - t0) * 1_000_000) / iters; +} + +function bench(label, mk, op) { + const samples = []; + for (let r = 0; r < RUNS; r++) { + samples.push( + nsPerOp(() => { + const x = mk(); + op(x); + }, ITERS), + ); + } + samples.sort((a, b) => a - b); + const median = samples[samples.length >> 1]; + const lo = samples[0]; + const hi = samples[samples.length - 1]; + return { label, median, lo, hi }; +} + +console.log("op,size,median_ns,lo_ns,hi_ns"); +for (const { name, size } of SIZES) { + // sliceCopy (current behavior) + const slice = bench( + "slice", + () => new Uint8Array(size), + (u8) => u8.slice(), + ); + // transfer (proposed) + const xfer = bench( + "transfer", + () => new Uint8Array(size), + (u8) => new Uint8Array(u8.buffer.transfer()), + ); + console.log(`slice,${name},${slice.median.toFixed(1)},${slice.lo.toFixed(1)},${slice.hi.toFixed(1)}`); + console.log(`transfer,${name},${xfer.median.toFixed(1)},${xfer.lo.toFixed(1)},${xfer.hi.toFixed(1)}`); +} diff --git a/tools/perf_research/fetch/h3_extract_body_copy/results.csv b/tools/perf_research/fetch/h3_extract_body_copy/results.csv new file mode 100644 index 00000000000000..05e927cf82924d --- /dev/null +++ b/tools/perf_research/fetch/h3_extract_body_copy/results.csv @@ -0,0 +1,11 @@ +op,size,median_ns,lo_ns,hi_ns +slice,32B,3368.6,2209.5,3623.8 +transfer,32B,2363.7,1674.5,2731.2 +slice,1KB,7001.6,4942.4,7867.6 +transfer,1KB,4145.9,3267.5,6289.8 +slice,64KB,57049.5,51772.5,74352.5 +transfer,64KB,26296.2,23374.4,32464.3 +slice,1MB,559138.6,496487.5,661038.3 +transfer,1MB,208249.5,204526.9,284866.4 +slice,4MB,2374300.6,2207344.6,2832406.4 +transfer,4MB,826431.0,815098.6,923094.2 diff --git a/tools/perf_research/fetch/micro/headers_micro.js b/tools/perf_research/fetch/micro/headers_micro.js new file mode 100644 index 00000000000000..1aae1c85c41e8d --- /dev/null +++ b/tools/perf_research/fetch/micro/headers_micro.js @@ -0,0 +1,92 @@ +// Microbench: Headers ops on a realistic header set (~12 entries). +// Tests: construct, get (hit), get (miss), append, iterate, has, delete. +// +// Run with each runtime: +// deno run -A micro/headers_micro.js +// node micro/headers_micro.js +// bun micro/headers_micro.js + +const ITERS = 1_000_000; + +function now() { + return performance.now(); +} + +const initObj = { + "Host": "example.com", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate, br", + "Content-Type": "application/json", + "Content-Length": "42", + "Connection": "keep-alive", + "Cookie": + "session=abc; theme=dark; lang=en; cart=1234; ref=https%3A%2F%2Fexample.com", + "Cache-Control": "no-cache", + "Origin": "https://example.com", + "Referer": "https://example.com/page", +}; + +function bench(name, fn) { + // warmup + for (let i = 0; i < 1000; i++) fn(i); + const t0 = now(); + for (let i = 0; i < ITERS; i++) fn(i); + const t1 = now(); + const ms = t1 - t0; + const nsPerOp = (ms * 1e6) / ITERS; + console.log(JSON.stringify({ name, ms: ms.toFixed(2), ns_per_op: nsPerOp.toFixed(1) })); +} + +// 1: Construct from object literal +bench("headers_construct_obj", () => { + new Headers(initObj); +}); + +// 2: Construct from entries array +const initArr = Object.entries(initObj); +bench("headers_construct_arr", () => { + new Headers(initArr); +}); + +// 3: get (case-mismatch hit) on a pre-built Headers +const builtHeaders = new Headers(initObj); +bench("headers_get_hit", () => { + builtHeaders.get("content-type"); +}); + +// 4: get miss +bench("headers_get_miss", () => { + builtHeaders.get("x-not-present"); +}); + +// 5: set on fresh +bench("headers_set_fresh", () => { + const h = new Headers(); + h.set("content-type", "application/json"); +}); + +// 6: append on fresh +bench("headers_append_fresh", () => { + const h = new Headers(); + h.append("set-cookie", "a=1"); + h.append("set-cookie", "b=2"); +}); + +// 7: iterate via for..of +bench("headers_iter", () => { + let s = 0; + for (const [k, v] of builtHeaders) s += k.length + v.length; + if (s < 0) console.log(s); +}); + +// 8: has hit +bench("headers_has_hit", () => { + builtHeaders.has("content-type"); +}); + +// 9: has miss +bench("headers_has_miss", () => { + builtHeaders.has("x-not-present"); +}); diff --git a/tools/perf_research/fetch/micro/request_response_micro.js b/tools/perf_research/fetch/micro/request_response_micro.js new file mode 100644 index 00000000000000..2ab21c558c78ad --- /dev/null +++ b/tools/perf_research/fetch/micro/request_response_micro.js @@ -0,0 +1,74 @@ +// Microbench: Request and Response object construction and body consumption. +// These objects are allocated on every HTTP request handled by Deno.serve. + +const ITERS = 200_000; + +function now() { + return performance.now(); +} + +function bench(name, fn) { + for (let i = 0; i < 1000; i++) fn(i); + const t0 = now(); + for (let i = 0; i < ITERS; i++) fn(i); + const t1 = now(); + const ms = t1 - t0; + const nsPerOp = (ms * 1e6) / ITERS; + console.log(JSON.stringify({ name, ms: ms.toFixed(2), ns_per_op: nsPerOp.toFixed(1) })); +} + +// 1: Construct Response from string +bench("response_construct_string", () => { + new Response("hello world"); +}); + +// 2: Construct Response from Uint8Array +const u8 = new TextEncoder().encode("hello world"); +bench("response_construct_u8", () => { + new Response(u8); +}); + +// 3: Construct Response with init headers (object literal) +bench("response_construct_with_headers", () => { + new Response("hello", { headers: { "content-type": "text/plain" } }); +}); + +// 4: Construct Response with init headers (Headers instance) +const reusedHeaders = new Headers({ "content-type": "text/plain" }); +bench("response_construct_reused_headers", () => { + new Response("hello", { headers: reusedHeaders }); +}); + +// 5: Construct Request +bench("request_construct", () => { + new Request("https://example.com/path?q=1", { + method: "POST", + headers: { "content-type": "application/json" }, + body: '{"a":1}', + }); +}); + +// 6: Read body via text() — full pipeline +const bodyBytes = new TextEncoder().encode( + JSON.stringify({ users: Array.from({ length: 10 }, (_, i) => ({ id: i, name: `u${i}` })) }), +); +const ITERS_BODY = 50_000; +async function benchAsync(name, fn) { + for (let i = 0; i < 100; i++) await fn(i); + const t0 = now(); + for (let i = 0; i < ITERS_BODY; i++) await fn(i); + const t1 = now(); + const ms = t1 - t0; + const nsPerOp = (ms * 1e6) / ITERS_BODY; + console.log(JSON.stringify({ name, ms: ms.toFixed(2), ns_per_op: nsPerOp.toFixed(1) })); +} + +await benchAsync("response_text_smallish", async () => { + const r = new Response(bodyBytes); + await r.text(); +}); + +await benchAsync("response_json_smallish", async () => { + const r = new Response(bodyBytes); + await r.json(); +}); diff --git a/tools/perf_research/fetch/micro_results.jsonl b/tools/perf_research/fetch/micro_results.jsonl new file mode 100644 index 00000000000000..997618dc2d1f85 --- /dev/null +++ b/tools/perf_research/fetch/micro_results.jsonl @@ -0,0 +1,48 @@ +{"name": "headers_construct_obj", "ms": "4244.71", "ns_per_op": 4244.7, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_construct_arr", "ms": "4127.76", "ns_per_op": 4127.8, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_get_hit", "ms": "303.21", "ns_per_op": 303.2, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_get_miss", "ms": "257.10", "ns_per_op": 257.1, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_set_fresh", "ms": "178.96", "ns_per_op": 179.0, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_append_fresh", "ms": "230.50", "ns_per_op": 230.5, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_iter", "ms": "28995.66", "ns_per_op": 28995.7, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_has_hit", "ms": "141.06", "ns_per_op": 141.1, "runtime": "deno", "script": "headers_micro.js"} +{"name": "headers_has_miss", "ms": "244.65", "ns_per_op": 244.7, "runtime": "deno", "script": "headers_micro.js"} +{"name": "response_construct_string", "ms": "25.30", "ns_per_op": 126.5, "runtime": "deno", "script": "request_response_micro.js"} +{"name": "response_construct_u8", "ms": "213.78", "ns_per_op": 1068.9, "runtime": "deno", "script": "request_response_micro.js"} +{"name": "response_construct_with_headers", "ms": "113.71", "ns_per_op": 568.6, "runtime": "deno", "script": "request_response_micro.js"} +{"name": "response_construct_reused_headers", "ms": "277.05", "ns_per_op": 1385.2, "runtime": "deno", "script": "request_response_micro.js"} +{"name": "request_construct", "ms": "728.00", "ns_per_op": 3640.0, "runtime": "deno", "script": "request_response_micro.js"} +{"name": "response_text_smallish", "ms": "81.27", "ns_per_op": 1625.5, "runtime": "deno", "script": "request_response_micro.js"} +{"name": "response_json_smallish", "ms": "196.90", "ns_per_op": 3937.9, "runtime": "deno", "script": "request_response_micro.js"} +{"name": "headers_construct_obj", "ms": "3690.83", "ns_per_op": 3690.8, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_construct_arr", "ms": "4946.01", "ns_per_op": 4946.0, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_get_hit", "ms": "94.68", "ns_per_op": 94.7, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_get_miss", "ms": "95.54", "ns_per_op": 95.5, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_set_fresh", "ms": "270.61", "ns_per_op": 270.6, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_append_fresh", "ms": "454.86", "ns_per_op": 454.9, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_iter", "ms": "397.87", "ns_per_op": 397.9, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_has_hit", "ms": "97.51", "ns_per_op": 97.5, "runtime": "node", "script": "headers_micro.js"} +{"name": "headers_has_miss", "ms": "93.03", "ns_per_op": 93.0, "runtime": "node", "script": "headers_micro.js"} +{"name": "response_construct_string", "ms": "1526.35", "ns_per_op": 7631.8, "runtime": "node", "script": "request_response_micro.js"} +{"name": "response_construct_u8", "ms": "2095.98", "ns_per_op": 10479.9, "runtime": "node", "script": "request_response_micro.js"} +{"name": "response_construct_with_headers", "ms": "1703.86", "ns_per_op": 8519.3, "runtime": "node", "script": "request_response_micro.js"} +{"name": "response_construct_reused_headers", "ms": "1707.69", "ns_per_op": 8538.5, "runtime": "node", "script": "request_response_micro.js"} +{"name": "request_construct", "ms": "2371.82", "ns_per_op": 11859.1, "runtime": "node", "script": "request_response_micro.js"} +{"name": "response_text_smallish", "ms": "546.50", "ns_per_op": 10930.0, "runtime": "node", "script": "request_response_micro.js"} +{"name": "response_json_smallish", "ms": "629.60", "ns_per_op": 12592.0, "runtime": "node", "script": "request_response_micro.js"} +{"name": "headers_construct_obj", "ms": "1281.85", "ns_per_op": 1281.8, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_construct_arr", "ms": "1638.35", "ns_per_op": 1638.3, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_get_hit", "ms": "36.81", "ns_per_op": 36.8, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_get_miss", "ms": "49.03", "ns_per_op": 49.0, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_set_fresh", "ms": "336.86", "ns_per_op": 336.9, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_append_fresh", "ms": "409.45", "ns_per_op": 409.4, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_iter", "ms": "1990.47", "ns_per_op": 1990.5, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_has_hit", "ms": "30.16", "ns_per_op": 30.2, "runtime": "bun", "script": "headers_micro.js"} +{"name": "headers_has_miss", "ms": "46.67", "ns_per_op": 46.7, "runtime": "bun", "script": "headers_micro.js"} +{"name": "response_construct_string", "ms": "1.02", "ns_per_op": 5.1, "runtime": "bun", "script": "request_response_micro.js"} +{"name": "response_construct_u8", "ms": "51.11", "ns_per_op": 255.6, "runtime": "bun", "script": "request_response_micro.js"} +{"name": "response_construct_with_headers", "ms": "129.09", "ns_per_op": 645.4, "runtime": "bun", "script": "request_response_micro.js"} +{"name": "response_construct_reused_headers", "ms": "88.22", "ns_per_op": 441.1, "runtime": "bun", "script": "request_response_micro.js"} +{"name": "request_construct", "ms": "266.53", "ns_per_op": 1332.6, "runtime": "bun", "script": "request_response_micro.js"} +{"name": "response_text_smallish", "ms": "36.72", "ns_per_op": 734.5, "runtime": "bun", "script": "request_response_micro.js"} +{"name": "response_json_smallish", "ms": "87.21", "ns_per_op": 1744.1, "runtime": "bun", "script": "request_response_micro.js"} diff --git a/tools/perf_research/fetch/post.lua b/tools/perf_research/fetch/post.lua new file mode 100644 index 00000000000000..c754501c388124 --- /dev/null +++ b/tools/perf_research/fetch/post.lua @@ -0,0 +1,3 @@ +wrk.method = "POST" +wrk.headers["Content-Type"] = "application/json" +wrk.body = '{"a":1,"b":2,"c":[1,2,3]}' diff --git a/tools/perf_research/fetch/profiles/headers_micro.prof.txt b/tools/perf_research/fetch/profiles/headers_micro.prof.txt new file mode 100644 index 00000000000000..539d42a45646ec --- /dev/null +++ b/tools/perf_research/fetch/profiles/headers_micro.prof.txt @@ -0,0 +1,164 @@ +Testing v8 version different from logging version +Statistical profiling result from /tmp/prof_test/v8.log, (34551 ticks, 6097 unaccounted, 0 excluded). + [Shared libraries]: + ticks total nonlib name + 9582 27.7% /var/agent-loop/repo/target/release/deno + 241 0.7% /usr/lib/x86_64-linux-gnu/libc.so.6 + 9 0.0% [vdso] + [JavaScript]: + ticks total nonlib name + 3068 8.9% 12.4% Builtin: StringToLowerCaseIntl + 3047 8.8% 12.3% Builtin: ArrayTimSort + 1913 5.5% 7.7% JS: * ext:deno_fetch/20_headers.js:272:7 + 1808 5.2% 7.3% Builtin: StringLessThan + 1581 4.6% 6.4% Builtin: KeyedStoreIC_Megamorphic + 1356 3.9% 5.5% Builtin: KeyedLoadIC_Megamorphic + 962 2.8% 3.9% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 607 1.8% 2.5% Builtin: CallFunction_ReceiverIsNullOrUndefined + 452 1.3% 1.8% Builtin: SortCompareUserFn + 434 1.3% 1.8% JS: * ext:deno_webidl/00_webidl.js:932:19 + 298 0.9% 1.2% Builtin: Store_FastObjectElements_0 + 282 0.8% 1.1% Builtin: Call_ReceiverIsNullOrUndefined + 279 0.8% 1.1% Builtin: Load_FastObjectElements_0 + 278 0.8% 1.1% Builtin: CreateShallowObjectLiteral + 272 0.8% 1.1% Builtin: ArrayIteratorPrototypeNext + 268 0.8% 1.1% Builtin: LoadIC + 233 0.7% 0.9% Builtin: ArrayPrototypeSort + 227 0.7% 0.9% Builtin: GrowFastSmiOrObjectElements + 174 0.5% 0.7% Builtin: ObjectPrototypeHasOwnProperty + 150 0.4% 0.6% Builtin: RecordWriteSaveFP + 146 0.4% 0.6% Builtin: StringEqual + 126 0.4% 0.5% Builtin: StringAdd_CheckNone + 68 0.2% 0.3% Builtin: ForInFilter + 64 0.2% 0.3% Builtin: CEntry_Return1_ArgvOnStack_NoBuiltinExit + 49 0.1% 0.2% Builtin: LoadICTrampoline + 43 0.1% 0.2% Builtin: ObjectHasOwn + 38 0.1% 0.2% Builtin: NumberToString + 24 0.1% 0.1% Builtin: ToString + 24 0.1% 0.1% Builtin: ArrayPrototypePush + 23 0.1% 0.1% Builtin: KeyedStoreICTrampoline_Megamorphic + 21 0.1% 0.1% Builtin: ToObject + 17 0.0% 0.1% Builtin: ArrayPrototypeJoinImpl + 15 0.0% 0.1% Builtin: JSConstructStubGeneric + 13 0.0% 0.1% Builtin: ForInEnumerate + 9 0.0% 0.0% JS: *entries ext:deno_webidl/00_webidl.js:1275:19 + 8 0.0% 0.0% Builtin: Typeof + 8 0.0% 0.0% Builtin: CanUseSameAccessor_FastObjectElements_0 + 6 0.0% 0.0% Builtin: CreateObjectFromSlowBoilerplateHelper + 5 0.0% 0.0% Builtin: DefineNamedOwnIC + 5 0.0% 0.0% Builtin: CallFunction_ReceiverIsAny + 5 0.0% 0.0% Builtin: ArrayPrototypeJoin + 4 0.0% 0.0% Builtin: AllocateInYoungGeneration + 3 0.0% 0.0% JS: *next ext:deno_webidl/00_webidl.js:1235:9 + 3 0.0% 0.0% Builtin: StringPrototypeCharCodeAt + 3 0.0% 0.0% Builtin: LoadIC_FunctionPrototype + 3 0.0% 0.0% Builtin: FastNewObject + 2 0.0% 0.0% Builtin: CreateObjectFromSlowBoilerplate + 1 0.0% 0.0% JS: + ext:deno_webidl/00_webidl.js:932:19 + 1 0.0% 0.0% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 1 0.0% 0.0% JS: + ext:deno_fetch/20_headers.js:272:7 + 1 0.0% 0.0% Builtin: StringPrototypeToLowerCaseIntl + 1 0.0% 0.0% Builtin: ObjectCreate + 1 0.0% 0.0% Builtin: LoadIC_NoFeedback + 1 0.0% 0.0% Builtin: LoadICGenericBaseline + 1 0.0% 0.0% Builtin: FunctionPrototypeCall + 1 0.0% 0.0% Builtin: Equal_Baseline + 1 0.0% 0.0% Builtin: Call_ReceiverIsNotNullOrUndefined + 1 0.0% 0.0% Builtin: CallBoundFunction + 1 0.0% 0.0% Builtin: CEntry_Return1_ArgvOnStack_BuiltinExit + [C++]: + ticks total nonlib name + 128 0.4% 0.5% syscall@@GLIBC_2.2.5 + 22 0.1% 0.1% __getpid@@GLIBC_2.2.5 + 15 0.0% 0.1% __libc_malloc@@GLIBC_2.2.5 + 4 0.0% 0.0% __lll_lock_wait_private@@GLIBC_PRIVATE + 3 0.0% 0.0% __lll_lock_wake_private@@GLIBC_PRIVATE + 3 0.0% 0.0% __libc_free@@GLIBC_2.2.5 + 3 0.0% 0.0% __exp2_finite@GLIBC_2.15 + 2 0.0% 0.0% __clock_gettime@@GLIBC_PRIVATE + 1 0.0% 0.0% malloc_usable_size@@GLIBC_2.2.5 + 1 0.0% 0.0% __pthread_mutex_unlock@GLIBC_2.2.5 + 1 0.0% 0.0% __pthread_mutex_lock@GLIBC_2.2.5 + 1 0.0% 0.0% __munmap@@GLIBC_PRIVATE + 1 0.0% 0.0% __mmap@@GLIBC_PRIVATE + 1 0.0% 0.0% _IO_default_xsputn@@GLIBC_2.2.5 + [Summary]: + ticks total nonlib name + 18436 53.4% 74.6% JavaScript + 186 0.5% 0.8% C++ + 807 2.3% 3.3% GC + 9832 28.5% Shared libraries + 6097 17.6% Unaccounted + [C++ entry points]: + ticks cpp total name + 3 37.5% 0.0% __libc_malloc@@GLIBC_2.2.5 + 1 12.5% 0.0% syscall@@GLIBC_2.2.5 + 1 12.5% 0.0% __pthread_mutex_unlock@GLIBC_2.2.5 + 1 12.5% 0.0% __pthread_mutex_lock@GLIBC_2.2.5 + 1 12.5% 0.0% __libc_free@@GLIBC_2.2.5 + 1 12.5% 0.0% __clock_gettime@@GLIBC_PRIVATE + [Bottom up (heavy) profile]: + Note: percentage shows a share of a particular caller in the total + amount of its parent calls. + Callers occupying less than 1.0% are not shown. + ticks parent name + 9582 27.7% /var/agent-loop/repo/target/release/deno + 4963 51.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 4963 100.0% Builtin: GeneratorPrototypeNext + 4026 42.0% /var/agent-loop/repo/target/release/deno + 3342 83.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 3342 100.0% Builtin: GeneratorPrototypeNext + 371 9.2% JS: *entries ext:deno_webidl/00_webidl.js:1275:19 + 371 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 371 100.0% Builtin: GeneratorPrototypeNext + 265 6.6% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 265 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 265 100.0% Builtin: GeneratorPrototypeNext + 6097 17.6% UNKNOWN + 5345 87.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 5345 100.0% Builtin: GeneratorPrototypeNext + 702 11.5% JS: * ext:deno_webidl/00_webidl.js:932:19 + 695 99.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 695 100.0% Builtin: GeneratorPrototypeNext + 3068 8.9% Builtin: StringToLowerCaseIntl + 3059 99.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 3059 100.0% Builtin: GeneratorPrototypeNext + 3047 8.8% Builtin: ArrayTimSort + 2963 97.2% Builtin: ArrayPrototypeSort + 2958 99.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 2958 100.0% Builtin: GeneratorPrototypeNext + 84 2.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 84 100.0% Builtin: GeneratorPrototypeNext + 1913 5.5% JS: * ext:deno_fetch/20_headers.js:272:7 + 1896 99.1% Builtin: ArrayPrototypeSort + 1893 99.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 1893 100.0% Builtin: GeneratorPrototypeNext + 1808 5.2% Builtin: StringLessThan + 1808 100.0% Builtin: ArrayPrototypeSort + 1805 99.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 1805 100.0% Builtin: GeneratorPrototypeNext + 1581 4.6% Builtin: KeyedStoreIC_Megamorphic + 1437 90.9% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 1437 100.0% Builtin: GeneratorPrototypeNext + 141 8.9% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 141 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 141 100.0% Builtin: GeneratorPrototypeNext + 1356 3.9% Builtin: KeyedLoadIC_Megamorphic + 1350 99.6% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 1350 100.0% Builtin: GeneratorPrototypeNext + 962 2.8% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 962 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 962 100.0% Builtin: GeneratorPrototypeNext + 607 1.8% Builtin: CallFunction_ReceiverIsNullOrUndefined + 533 87.8% Builtin: ArrayPrototypeSort + 532 99.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 532 100.0% Builtin: GeneratorPrototypeNext + 71 11.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 71 100.0% Builtin: GeneratorPrototypeNext + 452 1.3% Builtin: SortCompareUserFn + 451 99.8% Builtin: ArrayPrototypeSort + 449 99.6% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 449 100.0% Builtin: GeneratorPrototypeNext + 434 1.3% JS: * ext:deno_webidl/00_webidl.js:932:19 + 430 99.1% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/headers_micro.js:1:1 + 430 100.0% Builtin: GeneratorPrototypeNext diff --git a/tools/perf_research/fetch/profiles/headers_micro.v8.log.gz b/tools/perf_research/fetch/profiles/headers_micro.v8.log.gz new file mode 100644 index 00000000000000..70fb39f9fccb2d Binary files /dev/null and b/tools/perf_research/fetch/profiles/headers_micro.v8.log.gz differ diff --git a/tools/perf_research/fetch/profiles/headers_micro_deno.json b/tools/perf_research/fetch/profiles/headers_micro_deno.json new file mode 100644 index 00000000000000..30921f6bb7856e --- /dev/null +++ b/tools/perf_research/fetch/profiles/headers_micro_deno.json @@ -0,0 +1,9 @@ +{"name":"headers_construct_obj","ms":"4244.71","ns_per_op":"4244.7"} +{"name":"headers_construct_arr","ms":"4127.76","ns_per_op":"4127.8"} +{"name":"headers_get_hit","ms":"303.21","ns_per_op":"303.2"} +{"name":"headers_get_miss","ms":"257.10","ns_per_op":"257.1"} +{"name":"headers_set_fresh","ms":"178.96","ns_per_op":"179.0"} +{"name":"headers_append_fresh","ms":"230.50","ns_per_op":"230.5"} +{"name":"headers_iter","ms":"28995.66","ns_per_op":"28995.7"} +{"name":"headers_has_hit","ms":"141.06","ns_per_op":"141.1"} +{"name":"headers_has_miss","ms":"244.65","ns_per_op":"244.7"} diff --git a/tools/perf_research/fetch/profiles/request_response_micro.prof.txt b/tools/perf_research/fetch/profiles/request_response_micro.prof.txt new file mode 100644 index 00000000000000..d7177a48b6c561 --- /dev/null +++ b/tools/perf_research/fetch/profiles/request_response_micro.prof.txt @@ -0,0 +1,267 @@ +Testing v8 version different from logging version +(node:75337) ExperimentalWarning: VM Modules is an experimental feature and might change at any time +(Use `node --trace-warnings ...` to show where the warning was created) +Statistical profiling result from /tmp/prof_test2/v8.log, (1762 ticks, 237 unaccounted, 0 excluded). + [Shared libraries]: + ticks total nonlib name + 696 39.5% /var/agent-loop/repo/target/release/deno + 239 13.6% /usr/lib/x86_64-linux-gnu/libc.so.6 + [JavaScript]: + ticks total nonlib name + 50 2.8% 6.0% Builtin: LoadIC + 31 1.8% 3.7% Builtin: StringToLowerCaseIntl + 28 1.6% 3.4% JS: * ext:deno_webidl/00_webidl.js:821:19 + 27 1.5% 3.3% JS: * ext:deno_webidl/00_webidl.js:932:19 + 27 1.5% 3.3% Builtin: CreateShallowObjectLiteral + 23 1.3% 2.8% Builtin: StringAdd_CheckNone + 21 1.2% 2.5% Builtin: KeyedStoreIC + 19 1.1% 2.3% Builtin: ObjectPrototypeIsPrototypeOf + 15 0.9% 1.8% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 15 0.9% 1.8% Builtin: KeyedLoadIC_PolymorphicName + 13 0.7% 1.6% Builtin: KeyedLoadIC_Megamorphic + 11 0.6% 1.3% Builtin: ArrayPrototypePush + 9 0.5% 1.1% Builtin: LoadICTrampoline + 9 0.5% 1.1% Builtin: KeyedLoadIC + 8 0.5% 1.0% Builtin: CallFunction_ReceiverIsAny + 7 0.4% 0.8% Builtin: TypedArrayPrototypeSlice + 7 0.4% 0.8% Builtin: CreateTypedArray + 6 0.3% 0.7% Builtin: ObjectPrototypeHasOwnProperty + 5 0.3% 0.6% Builtin: StoreIC + 5 0.3% 0.6% Builtin: RunMicrotasks + 5 0.3% 0.6% Builtin: JSConstructStubGeneric + 5 0.3% 0.6% Builtin: GrowFastSmiOrObjectElements + 5 0.3% 0.6% Builtin: GetProperty + 5 0.3% 0.6% Builtin: Call_ReceiverIsNullOrUndefined + 5 0.3% 0.6% Builtin: CEntry_Return1_ArgvOnStack_NoBuiltinExit + 4 0.2% 0.5% Builtin: RecordWriteSaveFP + 4 0.2% 0.5% Builtin: KeyedStoreIC_Megamorphic + 4 0.2% 0.5% Builtin: KeyedLoadICTrampoline_Megamorphic + 4 0.2% 0.5% Builtin: KeyedLoadICTrampoline + 4 0.2% 0.5% Builtin: ArrayIteratorPrototypeNext + 3 0.2% 0.4% JS: +consumeBody ext:deno_fetch/22_body.js:254:29 + 3 0.2% 0.4% Builtin: StringPrototypeCharCodeAt + 3 0.2% 0.4% Builtin: NumberToString + 3 0.2% 0.4% Builtin: FulfillPromise + 3 0.2% 0.4% Builtin: FastNewObject + 3 0.2% 0.4% Builtin: Call_ReceiverIsNotNullOrUndefined + 3 0.2% 0.4% Builtin: CallFunction_ReceiverIsNotNullOrUndefined + 3 0.2% 0.4% Builtin: AsyncFunctionAwait + 3 0.2% 0.4% Builtin: ArrayPrototypeValues + 2 0.1% 0.2% JS: *consumeBody ext:deno_fetch/22_body.js:254:29 + 2 0.1% 0.2% Builtin: ToObject + 2 0.1% 0.2% Builtin: StoreICTrampoline + 2 0.1% 0.2% Builtin: PerformPromiseThen + 2 0.1% 0.2% Builtin: ObjectHasOwn + 2 0.1% 0.2% Builtin: KeyedStoreICTrampoline_Megamorphic + 2 0.1% 0.2% Builtin: JSBuiltinsConstructStub + 2 0.1% 0.2% Builtin: HasProperty + 2 0.1% 0.2% Builtin: ForInFilter + 2 0.1% 0.2% Builtin: FastNewClosure + 2 0.1% 0.2% Builtin: DefineNamedOwnIC + 2 0.1% 0.2% Builtin: CreateObjectFromSlowBoilerplate + 2 0.1% 0.2% Builtin: CallFunction_ReceiverIsNullOrUndefined + 2 0.1% 0.2% Builtin: CallBoundFunction + 2 0.1% 0.2% Builtin: CEntry_Return1_ArgvOnStack_BuiltinExit + 1 0.1% 0.1% JS: ^ ext:deno_fetch/20_headers.js:466:36 + 1 0.1% 0.1% JS: +next ext:deno_webidl/00_webidl.js:1235:9 + 1 0.1% 0.1% JS: +get headerList ext:deno_fetch/23_request.js:120:19 + 1 0.1% 0.1% JS: + ext:deno_webidl/00_webidl.js:821:19 + 1 0.1% 0.1% JS: *json ext:deno_fetch/22_body.js:348:27 + 1 0.1% 0.1% JS: *get headerList ext:deno_fetch/23_request.js:120:19 + 1 0.1% 0.1% JS: * ext:deno_webidl/00_webidl.js:913:10 + 1 0.1% 0.1% Builtin: Typeof + 1 0.1% 0.1% Builtin: StrictEqual_Generic_Baseline + 1 0.1% 0.1% Builtin: ResolvePromise + 1 0.1% 0.1% Builtin: PromiseFulfillReactionJob + 1 0.1% 0.1% Builtin: LoadICGenericBaseline + 1 0.1% 0.1% Builtin: KeyedStoreICTrampoline + 1 0.1% 0.1% Builtin: FunctionPrototypeCall + 1 0.1% 0.1% Builtin: ForInEnumerate + 1 0.1% 0.1% Builtin: CreateObjectFromSlowBoilerplateHelper + 1 0.1% 0.1% Builtin: Construct + 1 0.1% 0.1% Builtin: Call_ReceiverIsAny + 1 0.1% 0.1% Builtin: ArrayPrototypeSort + [C++]: + ticks total nonlib name + 55 3.1% 6.7% syscall@@GLIBC_2.2.5 + 29 1.6% 3.5% __lll_lock_wake_private@@GLIBC_PRIVATE + 14 0.8% 1.7% __lll_lock_wait_private@@GLIBC_PRIVATE + 10 0.6% 1.2% __libc_calloc@@GLIBC_2.2.5 + 9 0.5% 1.1% __libc_malloc@@GLIBC_2.2.5 + 6 0.3% 0.7% __munmap@@GLIBC_PRIVATE + 3 0.2% 0.4% __madvise@@GLIBC_PRIVATE + 2 0.1% 0.2% __libc_free@@GLIBC_2.2.5 + 1 0.1% 0.1% brk@@GLIBC_2.2.5 + 1 0.1% 0.1% __sbrk@@GLIBC_2.2.5 + 1 0.1% 0.1% __open@@GLIBC_2.2.5 + 1 0.1% 0.1% __mmap@@GLIBC_PRIVATE + 1 0.1% 0.1% __getpid@@GLIBC_2.2.5 + [Summary]: + ticks total nonlib name + 457 25.9% 55.3% JavaScript + 133 7.5% 16.1% C++ + 177 10.0% 21.4% GC + 935 53.1% Shared libraries + 237 13.5% Unaccounted + [C++ entry points]: + ticks cpp total name + 29 41.4% 1.6% __lll_lock_wake_private@@GLIBC_PRIVATE + 14 20.0% 0.8% __lll_lock_wait_private@@GLIBC_PRIVATE + 10 14.3% 0.6% __libc_calloc@@GLIBC_2.2.5 + 9 12.9% 0.5% __libc_malloc@@GLIBC_2.2.5 + 5 7.1% 0.3% syscall@@GLIBC_2.2.5 + 1 1.4% 0.1% brk@@GLIBC_2.2.5 + 1 1.4% 0.1% __sbrk@@GLIBC_2.2.5 + 1 1.4% 0.1% __libc_free@@GLIBC_2.2.5 + [Bottom up (heavy) profile]: + Note: percentage shows a share of a particular caller in the total + amount of its parent calls. + Callers occupying less than 1.0% are not shown. + ticks parent name + 696 39.5% /var/agent-loop/repo/target/release/deno + 500 71.8% /var/agent-loop/repo/target/release/deno + 251 50.2% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 251 100.0% Builtin: AsyncModuleEvaluate + 77 15.4% JS: * ext:deno_webidl/00_webidl.js:932:19 + 77 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 77 100.0% Builtin: AsyncModuleEvaluate + 73 14.6% Builtin: TypedArrayPrototypeSlice + 57 78.1% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 57 100.0% Builtin: AsyncModuleEvaluate + 12 16.4% Builtin: AsyncFunctionAwaitResolveClosure + 3 4.1% JS: +set method ext:deno_fetch/23_request.js:116:15 + 2 66.7% JS: ^benchAsync file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:56:26 + 2 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 1 33.3% Builtin: AsyncFunctionAwaitResolveClosure + 1 1.4% JS: ^bench file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:10:15 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 100.0% Builtin: AsyncModuleEvaluate + 55 11.0% JS: *consumeBody ext:deno_fetch/22_body.js:254:29 + 55 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 31 6.2% JS: +consumeBody ext:deno_fetch/22_body.js:254:29 + 31 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 96 13.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 96 100.0% Builtin: AsyncModuleEvaluate + 239 13.6% /usr/lib/x86_64-linux-gnu/libc.so.6 + 203 84.9% /var/agent-loop/repo/target/release/deno + 192 94.6% Builtin: TypedArrayPrototypeSlice + 143 74.5% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 143 100.0% Builtin: AsyncModuleEvaluate + 36 18.8% Builtin: AsyncFunctionAwaitResolveClosure + 9 4.7% JS: +set method ext:deno_fetch/23_request.js:116:15 + 7 77.8% Builtin: AsyncFunctionAwaitResolveClosure + 2 22.2% JS: ^benchAsync file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:56:26 + 2 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 2 1.0% JS: ~benchAsync file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:56:26 + 2 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 2 1.0% JS: ^bench file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:10:15 + 2 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 2 100.0% Builtin: AsyncModuleEvaluate + 5 2.5% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 5 100.0% Builtin: AsyncModuleEvaluate + 237 13.5% UNKNOWN + 196 82.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 196 100.0% Builtin: AsyncModuleEvaluate + 14 5.9% JS: * ext:deno_webidl/00_webidl.js:932:19 + 14 100.0% JS: * ext:deno_webidl/00_webidl.js:932:19 + 14 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 14 100.0% Builtin: AsyncModuleEvaluate + 11 4.6% Builtin: AsyncFunctionAwaitResolveClosure + 6 2.5% JS: * ext:deno_webidl/00_webidl.js:821:19 + 6 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 6 100.0% Builtin: AsyncModuleEvaluate + 3 1.3% JS: ^bench file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:10:15 + 3 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 3 100.0% Builtin: AsyncModuleEvaluate + 55 3.1% syscall@@GLIBC_2.2.5 + 5 9.1% /var/agent-loop/repo/target/release/deno + 1 20.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 100.0% Builtin: AsyncModuleEvaluate + 1 20.0% JS: ~benchAsync file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:56:26 + 1 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 1 20.0% JS: ^Request ext:deno_fetch/23_request.js:321:14 + 1 100.0% JS: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:43:28 + 1 100.0% JS: ^bench file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:10:15 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 20.0% JS: +json ext:deno_fetch/22_body.js:348:27 + 1 100.0% JS: ^ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:71:44 + 1 100.0% JS: ^benchAsync file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:56:26 + 1 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 1 20.0% JS: +consumeBody ext:deno_fetch/22_body.js:254:29 + 1 100.0% JS: +text ext:deno_fetch/22_body.js:358:27 + 1 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 50 2.8% Builtin: LoadIC + 37 74.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 37 100.0% Builtin: AsyncModuleEvaluate + 4 8.0% JS: * ext:deno_webidl/00_webidl.js:821:19 + 4 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 4 100.0% Builtin: AsyncModuleEvaluate + 4 8.0% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 4 100.0% JS: * ext:deno_webidl/00_webidl.js:821:19 + 4 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 4 100.0% Builtin: AsyncModuleEvaluate + 3 6.0% JS: *get headerList ext:deno_fetch/23_request.js:120:19 + 3 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 3 100.0% Builtin: AsyncModuleEvaluate + 1 2.0% JS: * ext:deno_webidl/00_webidl.js:932:19 + 1 100.0% JS: * ext:deno_webidl/00_webidl.js:932:19 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 100.0% Builtin: AsyncModuleEvaluate + 1 2.0% Builtin: AsyncFunctionAwaitResolveClosure + 31 1.8% Builtin: StringToLowerCaseIntl + 30 96.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 30 100.0% Builtin: AsyncModuleEvaluate + 1 3.2% JS: +next ext:deno_webidl/00_webidl.js:1235:9 + 1 100.0% JS: + ext:deno_webidl/00_webidl.js:932:19 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 100.0% Builtin: AsyncModuleEvaluate + 29 1.6% __lll_lock_wake_private@@GLIBC_PRIVATE + 29 100.0% /var/agent-loop/repo/target/release/deno + 29 100.0% Builtin: TypedArrayPrototypeSlice + 20 69.0% Builtin: AsyncFunctionAwaitResolveClosure + 9 31.0% JS: +set method ext:deno_fetch/23_request.js:116:15 + 9 100.0% Builtin: AsyncFunctionAwaitResolveClosure + 28 1.6% JS: * ext:deno_webidl/00_webidl.js:821:19 + 28 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 28 100.0% Builtin: AsyncModuleEvaluate + 27 1.5% JS: * ext:deno_webidl/00_webidl.js:932:19 + 18 66.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 18 100.0% Builtin: AsyncModuleEvaluate + 9 33.3% JS: * ext:deno_webidl/00_webidl.js:932:19 + 9 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 9 100.0% Builtin: AsyncModuleEvaluate + 27 1.5% Builtin: CreateShallowObjectLiteral + 22 81.5% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 22 100.0% Builtin: AsyncModuleEvaluate + 4 14.8% JS: * ext:deno_webidl/00_webidl.js:821:19 + 4 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 4 100.0% Builtin: AsyncModuleEvaluate + 1 3.7% JS: * ext:deno_webidl/00_webidl.js:932:19 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 100.0% Builtin: AsyncModuleEvaluate + 23 1.3% Builtin: StringAdd_CheckNone + 22 95.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 22 100.0% Builtin: AsyncModuleEvaluate + 1 4.3% JS: * ext:deno_webidl/00_webidl.js:932:19 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 100.0% Builtin: AsyncModuleEvaluate + 21 1.2% Builtin: KeyedStoreIC + 10 47.6% JS: * ext:deno_webidl/00_webidl.js:932:19 + 10 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 10 100.0% Builtin: AsyncModuleEvaluate + 7 33.3% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 7 100.0% Builtin: AsyncModuleEvaluate + 3 14.3% JS: * ext:deno_webidl/00_webidl.js:821:19 + 3 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 3 100.0% Builtin: AsyncModuleEvaluate + 1 4.8% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 1 100.0% JS: * ext:deno_webidl/00_webidl.js:821:19 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 1 100.0% Builtin: AsyncModuleEvaluate + 19 1.1% Builtin: ObjectPrototypeIsPrototypeOf + 14 73.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/micro/request_response_micro.js:1:1 + 14 100.0% Builtin: AsyncModuleEvaluate + 3 15.8% Builtin: AsyncFunctionAwaitResolveClosure + 1 5.3% JS: +consumeBody ext:deno_fetch/22_body.js:254:29 + 1 100.0% JS: +json ext:deno_fetch/22_body.js:348:27 + 1 100.0% Builtin: AsyncFunctionAwaitResolveClosure diff --git a/tools/perf_research/fetch/profiles/request_response_micro.v8.log.gz b/tools/perf_research/fetch/profiles/request_response_micro.v8.log.gz new file mode 100644 index 00000000000000..e37c4c0976a992 Binary files /dev/null and b/tools/perf_research/fetch/profiles/request_response_micro.v8.log.gz differ diff --git a/tools/perf_research/fetch/profiles/request_response_micro_deno.json b/tools/perf_research/fetch/profiles/request_response_micro_deno.json new file mode 100644 index 00000000000000..4efe73925d6a7a --- /dev/null +++ b/tools/perf_research/fetch/profiles/request_response_micro_deno.json @@ -0,0 +1,7 @@ +{"name":"response_construct_string","ms":"25.30","ns_per_op":"126.5"} +{"name":"response_construct_u8","ms":"213.78","ns_per_op":"1068.9"} +{"name":"response_construct_with_headers","ms":"113.71","ns_per_op":"568.6"} +{"name":"response_construct_reused_headers","ms":"277.05","ns_per_op":"1385.2"} +{"name":"request_construct","ms":"728.00","ns_per_op":"3640.0"} +{"name":"response_text_smallish","ms":"81.27","ns_per_op":"1625.5"} +{"name":"response_json_smallish","ms":"196.90","ns_per_op":"3937.9"} diff --git a/tools/perf_research/fetch/profiles/server_hello.prof.txt b/tools/perf_research/fetch/profiles/server_hello.prof.txt new file mode 100644 index 00000000000000..2b9f936dd2ce4f --- /dev/null +++ b/tools/perf_research/fetch/profiles/server_hello.prof.txt @@ -0,0 +1,134 @@ +Testing v8 version different from logging version +(node:75388) ExperimentalWarning: VM Modules is an experimental feature and might change at any time +(Use `node --trace-warnings ...` to show where the warning was created) +Statistical profiling result from /tmp/prof_server/v8.log, (2619 ticks, 284 unaccounted, 0 excluded). + [Shared libraries]: + ticks total nonlib name + 1371 52.3% /var/agent-loop/repo/target/release/deno + 109 4.2% /usr/lib/x86_64-linux-gnu/libc.so.6 + [JavaScript]: + ticks total nonlib name + 98 3.7% 8.6% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 57 2.2% 5.0% Builtin: KeyedStoreIC + 48 1.8% 4.2% Builtin: LoadIC + 45 1.7% 4.0% Builtin: CreateTypedArray + 41 1.6% 3.6% Builtin: CreateShallowObjectLiteral + 37 1.4% 3.2% Builtin: RunMicrotasks + 35 1.3% 3.1% Builtin: GetProperty + 34 1.3% 3.0% Builtin: TypedArrayPrototypeSlice + 33 1.3% 2.9% Builtin: ObjectPrototypeIsPrototypeOf + 30 1.1% 2.6% Builtin: LoadICTrampoline + 22 0.8% 1.9% Builtin: JSConstructStubGeneric + 20 0.8% 1.8% Builtin: AsyncFunctionAwait + 18 0.7% 1.6% Builtin: ObjectPrototypeHasOwnProperty + 17 0.6% 1.5% Builtin: ResolvePromise + 17 0.6% 1.5% Builtin: PerformPromiseThen + 17 0.6% 1.5% Builtin: KeyedLoadIC + 17 0.6% 1.5% Builtin: EnqueueMicrotask + 14 0.5% 1.2% Builtin: KeyedLoadIC_PolymorphicName + 14 0.5% 1.2% Builtin: CallApiCallbackOptimizedNoProfiling + 12 0.5% 1.1% Builtin: Construct + 11 0.4% 1.0% JS: *get ext:deno_fetch/22_body.js:290:10 + 11 0.4% 1.0% Builtin: ForInEnumerate + 10 0.4% 0.9% Builtin: ObjectHasOwn + 10 0.4% 0.9% Builtin: GrowFastSmiOrObjectElements + 8 0.3% 0.7% Builtin: FastNewObject + 8 0.3% 0.7% Builtin: CallFunction_ReceiverIsAny + 7 0.3% 0.6% Builtin: ResumeGeneratorTrampoline + 7 0.3% 0.6% Builtin: PromisePrototypeThen + 7 0.3% 0.6% Builtin: FulfillPromise + 6 0.2% 0.5% Builtin: StringToLowerCaseIntl + 6 0.2% 0.5% Builtin: JSRunMicrotasksEntry + 6 0.2% 0.5% Builtin: ConstructFunction + 6 0.2% 0.5% Builtin: ArrayBufferConstructor + 5 0.2% 0.4% Builtin: StringSubstring + 5 0.2% 0.4% Builtin: JSEntry + 4 0.2% 0.4% Builtin: StringAdd_CheckNone + 4 0.2% 0.4% Builtin: KeyedStoreICTrampoline + 3 0.1% 0.3% Builtin: StringEqual + 3 0.1% 0.3% Builtin: CallFunction_ReceiverIsNullOrUndefined + 3 0.1% 0.3% Builtin: AsyncFunctionAwaitResolveClosure + 3 0.1% 0.3% Builtin: AdaptorWithBuiltinExitFrame1 + 2 0.1% 0.2% JS: +get ext:deno_fetch/22_body.js:290:10 + 2 0.1% 0.2% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 2 0.1% 0.2% Builtin: Typeof + 2 0.1% 0.2% Builtin: TypedArrayPrototypeByteLength + 2 0.1% 0.2% Builtin: StringPrototypeSlice + 2 0.1% 0.2% Builtin: PromiseFulfillReactionJob + 2 0.1% 0.2% Builtin: LoadICGenericBaseline + 2 0.1% 0.2% Builtin: JSBuiltinsConstructStub + 2 0.1% 0.2% Builtin: ForInFilter + 2 0.1% 0.2% Builtin: Call_ReceiverIsNullOrUndefined + 1 0.0% 0.1% JS: ~ file:///var/agent-loop/repo/tools/perf_research/fetch/servers/deno_server.js:10:42 + 1 0.0% 0.1% Builtin: ToBooleanForBaselineJump + 1 0.0% 0.1% Builtin: StringPrototypeCharCodeAt + 1 0.0% 0.1% Builtin: KeyedLoadICTrampoline + 1 0.0% 0.1% Builtin: FunctionPrototypeCall + 1 0.0% 0.1% Builtin: ForInPrepare + 1 0.0% 0.1% Builtin: Call_ReceiverIsAny + 1 0.0% 0.1% Builtin: CallFunction_ReceiverIsNotNullOrUndefined + 1 0.0% 0.1% Builtin: AsyncFunctionEnter + [C++]: + ticks total nonlib name + 28 1.1% 2.5% __libc_malloc@@GLIBC_2.2.5 + 15 0.6% 1.3% __libc_free@@GLIBC_2.2.5 + 11 0.4% 1.0% syscall@@GLIBC_2.2.5 + 7 0.3% 0.6% __libc_calloc@@GLIBC_2.2.5 + 3 0.1% 0.3% clock_nanosleep@GLIBC_2.2.5 + 1 0.0% 0.1% __read_nocancel@@GLIBC_PRIVATE + 1 0.0% 0.1% __mmap@@GLIBC_PRIVATE + 1 0.0% 0.1% __madvise@@GLIBC_PRIVATE + [Summary]: + ticks total nonlib name + 788 30.1% 69.2% JavaScript + 67 2.6% 5.9% C++ + 43 1.6% 3.8% GC + 1480 56.5% Shared libraries + 284 10.8% Unaccounted + [C++ entry points]: + ticks cpp total name + 26 49.1% 1.0% __libc_malloc@@GLIBC_2.2.5 + 15 28.3% 0.6% __libc_free@@GLIBC_2.2.5 + 7 13.2% 0.3% __libc_calloc@@GLIBC_2.2.5 + 3 5.7% 0.1% clock_nanosleep@GLIBC_2.2.5 + 2 3.8% 0.1% syscall@@GLIBC_2.2.5 + [Bottom up (heavy) profile]: + Note: percentage shows a share of a particular caller in the total + amount of its parent calls. + Callers occupying less than 1.0% are not shown. + ticks parent name + 1371 52.3% /var/agent-loop/repo/target/release/deno + 370 27.0% /var/agent-loop/repo/target/release/deno + 203 54.9% Builtin: TypedArrayPrototypeSlice + 161 11.7% Builtin: AsyncFunctionAwaitResolveClosure + 284 10.8% UNKNOWN + 34 12.0% Builtin: AsyncFunctionAwaitResolveClosure + 3 1.1% JS: ^ file:///var/agent-loop/repo/tools/perf_research/fetch/servers/deno_server.js:10:42 + 109 4.2% /usr/lib/x86_64-linux-gnu/libc.so.6 + 30 27.5% /var/agent-loop/repo/target/release/deno + 29 96.7% Builtin: TypedArrayPrototypeSlice + 3 2.8% Builtin: TypedArrayPrototypeSlice + 1 33.3% JS: ^ file:///var/agent-loop/repo/tools/perf_research/fetch/servers/deno_server.js:10:42 + 3 2.8% Builtin: AsyncFunctionAwaitResolveClosure + 98 3.7% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 57 2.2% Builtin: KeyedStoreIC + 25 43.9% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 1 1.8% JS: ^ file:///var/agent-loop/repo/tools/perf_research/fetch/servers/deno_server.js:10:42 + 1 1.8% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 48 1.8% Builtin: LoadIC + 45 1.7% Builtin: CreateTypedArray + 45 100.0% Builtin: TypedArrayPrototypeSlice + 41 1.6% Builtin: CreateShallowObjectLiteral + 4 9.8% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 1 2.4% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 37 1.4% Builtin: RunMicrotasks + 35 1.3% Builtin: GetProperty + 34 1.3% Builtin: TypedArrayPrototypeSlice + 1 2.9% JS: ^ file:///var/agent-loop/repo/tools/perf_research/fetch/servers/deno_server.js:10:42 + 33 1.3% Builtin: ObjectPrototypeIsPrototypeOf + 1 3.0% Builtin: AsyncFunctionAwaitResolveClosure + 30 1.1% Builtin: LoadICTrampoline + 28 1.1% __libc_malloc@@GLIBC_2.2.5 + 10 35.7% /var/agent-loop/repo/target/release/deno + 10 100.0% Builtin: TypedArrayPrototypeSlice + 1 3.6% Builtin: AsyncFunctionAwaitResolveClosure diff --git a/tools/perf_research/fetch/profiles/server_hello.v8.log.gz b/tools/perf_research/fetch/profiles/server_hello.v8.log.gz new file mode 100644 index 00000000000000..4f00d7e8d5c265 Binary files /dev/null and b/tools/perf_research/fetch/profiles/server_hello.v8.log.gz differ diff --git a/tools/perf_research/fetch/results.jsonl b/tools/perf_research/fetch/results.jsonl new file mode 100644 index 00000000000000..cc1e4b4f5c346c --- /dev/null +++ b/tools/perf_research/fetch/results.jsonl @@ -0,0 +1,12 @@ +{"label":"deno_hello","method":"GET","url":"http://127.0.0.1:8080/hello","rps":"39775.16","lat_avg":"1.63ms","lat_p99":"4.33ms","non2xx":"0"} +{"label":"deno_headers","method":"GET","url":"http://127.0.0.1:8080/headers","rps":"33803.13","lat_avg":"1.91ms","lat_p99":"4.03ms","non2xx":"0"} +{"label":"deno_echo_small","method":"POST","url":"http://127.0.0.1:8080/echo","rps":"30408.38","lat_avg":"2.10ms","lat_p99":"3.40ms","non2xx":"0"} +{"label":"deno_bigbody","method":"GET","url":"http://127.0.0.1:8080/bigbody","rps":"1246.27","lat_avg":"51.21ms","lat_p99":"80.21ms","non2xx":"0"} +{"label":"node_hello","method":"GET","url":"http://127.0.0.1:8081/hello","rps":"32225.68","lat_avg":"2.27ms","lat_p99":"4.67ms","non2xx":"0"} +{"label":"node_headers","method":"GET","url":"http://127.0.0.1:8081/headers","rps":"37094.63","lat_avg":"1.75ms","lat_p99":"3.23ms","non2xx":"0"} +{"label":"node_echo_small","method":"POST","url":"http://127.0.0.1:8081/echo","rps":"18456.21","lat_avg":"3.46ms","lat_p99":"5.50ms","non2xx":"0"} +{"label":"node_bigbody","method":"GET","url":"http://127.0.0.1:8081/bigbody","rps":"2435.40","lat_avg":"32.72ms","lat_p99":"335.22ms","non2xx":"0"} +{"label":"bun_hello","method":"GET","url":"http://127.0.0.1:8082/hello","rps":"69190.38","lat_avg":"0.94ms","lat_p99":"2.16ms","non2xx":"0"} +{"label":"bun_headers","method":"GET","url":"http://127.0.0.1:8082/headers","rps":"55933.44","lat_avg":"1.14ms","lat_p99":"2.30ms","non2xx":"0"} +{"label":"bun_echo_small","method":"POST","url":"http://127.0.0.1:8082/echo","rps":"51143.70","lat_avg":"1.26ms","lat_p99":"2.55ms","non2xx":"0"} +{"label":"bun_bigbody","method":"GET","url":"http://127.0.0.1:8082/bigbody","rps":"1081.59","lat_avg":"58.86ms","lat_p99":"108.30ms","non2xx":"0"} diff --git a/tools/perf_research/fetch/run_micro.sh b/tools/perf_research/fetch/run_micro.sh new file mode 100755 index 00000000000000..35eee90a434888 --- /dev/null +++ b/tools/perf_research/fetch/run_micro.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Run microbenches in all three runtimes. +# Usage: bash run_micro.sh +set -euo pipefail +cd "$(dirname "$0")" + +DENO_BIN="${DENO_BIN:-../../../target/release/deno}" +NODE_BIN="${NODE_BIN:-node}" +BUN_BIN="${BUN_BIN:-bun}" + +OUT="micro_results.jsonl" +: > "$OUT" + +run_one() { + local script=$1 + echo "===== $script =====" + for rt in deno node bun; do + case $rt in + deno) cmd="$DENO_BIN run -A --no-prompt $script" ;; + node) cmd="$NODE_BIN $script" ;; + bun) cmd="$BUN_BIN $script" ;; + esac + while IFS= read -r line; do + printf '%s\n' "$line" | jq --arg rt "$rt" --arg script "$(basename $script)" '. + {runtime: $rt, script: $script}' >> "$OUT" 2>/dev/null || \ + printf '{"runtime":"%s","script":"%s","raw":%s}\n' "$rt" "$(basename $script)" "$(printf '%s' "$line" | jq -Rs .)" >> "$OUT" + done < <($cmd 2>/dev/null) + done +} + +run_one "micro/headers_micro.js" +run_one "micro/request_response_micro.js" + +echo +echo "--- summary ---" +cat "$OUT" diff --git a/tools/perf_research/fetch/run_servers.sh b/tools/perf_research/fetch/run_servers.sh new file mode 100755 index 00000000000000..e4581411ebd3dd --- /dev/null +++ b/tools/perf_research/fetch/run_servers.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# Launch all three servers and benchmark them with wrk on each route. +# Outputs JSON-lines results to results.jsonl in this directory. +# +# Usage: bash run_servers.sh [duration_seconds] [connections] +# +# Requires: ./target/release/deno, node, bun, wrk on PATH. + +set -uo pipefail +cd "$(dirname "$0")" + +DENO_BIN="${DENO_BIN:-../../../target/release/deno}" +NODE_BIN="${NODE_BIN:-node}" +BUN_BIN="${BUN_BIN:-bun}" + +DURATION="${1:-10}" +CONNS="${2:-64}" +THREADS=4 + +OUT="results.jsonl" +: > "$OUT" + +versions() { + echo "deno=$($DENO_BIN --version | head -1)" + echo "node=$($NODE_BIN --version)" + echo "bun=$($BUN_BIN --version)" +} +versions > versions.txt +cat versions.txt + +# pre-cleanup any lingering listeners +for p in 8080 8081 8082; do + pid=$(ss -lntp 2>/dev/null | awk -v port=":$p" '$4 ~ port {sub(/.*pid=/,""); sub(/,.*/,""); print}') + if [ -n "$pid" ]; then kill -9 "$pid" 2>/dev/null || true; fi +done +sleep 1 + +wait_port() { + local port=$1 + for i in $(seq 1 50); do + if exec 3<>/dev/tcp/127.0.0.1/$port 2>/dev/null; then + exec 3<&-; exec 3>&- + return 0 + fi + sleep 0.2 + done + echo "port $port never came up" + return 1 +} + +# Lua script for POST. wrk substitutes %BODY% via shell. +cat > post.lua <<'EOF' +wrk.method = "POST" +wrk.headers["Content-Type"] = "application/json" +wrk.body = '{"a":1,"b":2,"c":[1,2,3]}' +EOF + +run_wrk() { + local label=$1 url=$2 method=${3:-GET} + local extra="" + if [ "$method" = "POST" ]; then extra="-s post.lua"; fi + echo "==> $label $method $url" + out=$(wrk -t$THREADS -c$CONNS -d${DURATION}s --latency $extra "$url" 2>&1 || true) + echo "$out" + rps=$(echo "$out" | awk '/Requests\/sec/ {print $2}') + lat_avg=$(echo "$out" | awk '/^ Latency/ {print $2; exit}') + # Latency p99 lives in a Latency Distribution block; grab the FIRST 99% match + lat_p99=$(echo "$out" | awk '/Latency Distribution/{f=1; next} f && /99%/ {print $2; exit}') + non2xx=$(echo "$out" | awk '/Non-2xx or 3xx/ {print $5; exit}') + printf '{"label":"%s","method":"%s","url":"%s","rps":"%s","lat_avg":"%s","lat_p99":"%s","non2xx":"%s"}\n' \ + "$label" "$method" "$url" "$rps" "$lat_avg" "$lat_p99" "${non2xx:-0}" >> "$OUT" +} + +run_runtime() { + local name=$1 port=$2 cmd=$3 + local logfile="server_${name}.log" + echo "===== $name on port $port =====" + bash -c "exec $cmd" > "$logfile" 2>&1 & + local pid=$! + if ! wait_port "$port"; then + echo "server failed; log:" && cat "$logfile" | head -20 + kill -9 "$pid" 2>/dev/null || true + return + fi + sleep 1 + + run_wrk "${name}_hello" "http://127.0.0.1:${port}/hello" + run_wrk "${name}_headers" "http://127.0.0.1:${port}/headers" + run_wrk "${name}_echo_small" "http://127.0.0.1:${port}/echo" POST + run_wrk "${name}_bigbody" "http://127.0.0.1:${port}/bigbody" + + kill -9 "$pid" 2>/dev/null || true + # Give the kernel time to release the port + sleep 2 +} + +run_runtime deno 8080 "$DENO_BIN run -A --no-prompt servers/deno_server.js --port=8080" +run_runtime node 8081 "$NODE_BIN servers/node_server.mjs --port=8081" +run_runtime bun 8082 "$BUN_BIN servers/bun_server.js --port=8082" + +echo +echo "--- summary ---" +cat "$OUT" diff --git a/tools/perf_research/fetch/run_v8_prof.sh b/tools/perf_research/fetch/run_v8_prof.sh new file mode 100755 index 00000000000000..f998330b45c7ee --- /dev/null +++ b/tools/perf_research/fetch/run_v8_prof.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Run a Deno microbench with --v8-flags=--prof, then post-process via +# `node --prof-process` to produce a readable tick summary committed to +# profiles/. This is the attribution mechanism used in the report. +# +# Usage: bash run_v8_prof.sh +set -euo pipefail +cd "$(dirname "$0")" +DENO_BIN="${DENO_BIN:-../../../target/release/deno}" +NODE_BIN="${NODE_BIN:-node}" +mkdir -p profiles + +run_prof() { + local tag=$1 script=$2 + local workdir + workdir=$(mktemp -d) + echo "==> $tag: $DENO_BIN run -A --v8-flags=--prof $script (workdir=$workdir)" + ( cd "$workdir" && "$DENO_BIN" run -A --no-prompt --v8-flags=--prof,--no-logfile-per-isolate "$OLDPWD/$script" ) > "profiles/${tag}.stdout.txt" 2> "profiles/${tag}.stderr.txt" || true + local log + log=$(find "$workdir" -maxdepth 1 -name '*-v8.log' -o -name 'v8.log' | head -1) + if [ -z "$log" ]; then echo "no v8 log produced for $tag (workdir=$workdir)"; ls "$workdir"; return 1; fi + cp "$log" "profiles/${tag}.v8.log" + $NODE_BIN --prof-process "profiles/${tag}.v8.log" > "profiles/${tag}.prof.txt" 2>&1 || true + echo "--- top entries for $tag ---" + awk '/\[Bottom up \(heavy\) profile\]/{f=1} f' "profiles/${tag}.prof.txt" | head -40 +} + +run_prof headers_micro micro/headers_micro.js +run_prof request_response_micro micro/request_response_micro.js diff --git a/tools/perf_research/fetch/servers/bun_server.js b/tools/perf_research/fetch/servers/bun_server.js new file mode 100644 index 00000000000000..3caaa008024311 --- /dev/null +++ b/tools/perf_research/fetch/servers/bun_server.js @@ -0,0 +1,47 @@ +// HTTP server using Bun.serve. +// Run: bun servers/bun_server.js --port=8082 +const port = Number( + Bun.argv.find((a) => a.startsWith("--port="))?.slice(7), +) || 8082; + +const helloBytes = new TextEncoder().encode( + JSON.stringify({ ok: true, msg: "hello" }), +); + +Bun.serve({ + port, + async fetch(req) { + const url = new URL(req.url); + switch (url.pathname) { + case "/hello": + return new Response(helloBytes, { + headers: { "content-type": "application/json" }, + }); + case "/echo": { + const body = await req.json(); + return new Response(JSON.stringify(body), { + headers: { "content-type": "application/json" }, + }); + } + case "/echo-bytes": { + const buf = await req.arrayBuffer(); + return new Response(buf, { + headers: { "content-type": "application/octet-stream" }, + }); + } + case "/headers": { + let count = 0; + for (const _h of req.headers) count++; + return new Response(`${count}`); + } + case "/bigbody": { + const buf = new Uint8Array(1 << 20).fill(0x41); + return new Response(buf, { + headers: { "content-type": "application/octet-stream" }, + }); + } + default: + return new Response("not found", { status: 404 }); + } + }, +}); diff --git a/tools/perf_research/fetch/servers/deno_server.js b/tools/perf_research/fetch/servers/deno_server.js new file mode 100644 index 00000000000000..6481932a36d7ba --- /dev/null +++ b/tools/perf_research/fetch/servers/deno_server.js @@ -0,0 +1,47 @@ +// HTTP server using Deno.serve. JSON echo / simple text routes. +// Run: deno run -A --no-prompt servers/deno_server.js [--port=8080] +const port = Number(Deno.args.find((a) => a.startsWith("--port="))?.slice(7)) || + 8080; + +const helloBytes = new TextEncoder().encode( + JSON.stringify({ ok: true, msg: "hello" }), +); + +Deno.serve({ port, onListen: () => {} }, async (req) => { + const url = new URL(req.url); + switch (url.pathname) { + case "/hello": { + return new Response(helloBytes, { + headers: { "content-type": "application/json" }, + }); + } + case "/echo": { + const body = await req.json(); + return new Response(JSON.stringify(body), { + headers: { "content-type": "application/json" }, + }); + } + case "/echo-bytes": { + const buf = await req.arrayBuffer(); + return new Response(buf, { + headers: { "content-type": "application/octet-stream" }, + }); + } + case "/headers": { + // Force consumption of all headers + let count = 0; + for (const _h of req.headers) count++; + return new Response(`${count}`); + } + case "/bigbody": { + // 1 MB ASCII body. Server allocates a fresh buffer each request to + // exercise the response body construction path. + const buf = new Uint8Array(1 << 20).fill(0x41); + return new Response(buf, { + headers: { "content-type": "application/octet-stream" }, + }); + } + default: + return new Response("not found", { status: 404 }); + } +}); diff --git a/tools/perf_research/fetch/servers/deno_server_stdrouter.js b/tools/perf_research/fetch/servers/deno_server_stdrouter.js new file mode 100644 index 00000000000000..08b179fb569f3e --- /dev/null +++ b/tools/perf_research/fetch/servers/deno_server_stdrouter.js @@ -0,0 +1,17 @@ +// HTTP server using Deno.serve, with Request/Response semantics (Web Fetch surface). +// This stresses the Web Fetch Request/Response/Headers JS layer on the server side, +// in contrast to deno_server.js which uses the same surface but is the same path. +// Kept for future variants (e.g. router-style). +const port = Number(Deno.args.find((a) => a.startsWith("--port="))?.slice(7)) || + 8083; + +Deno.serve({ port, onListen: () => {} }, (req) => { + const url = new URL(req.url); + const out = new Headers(); + out.set("x-original-method", req.method); + out.set("x-original-path", url.pathname); + for (const [k, v] of req.headers) { + out.append(`x-echo-${k}`, v); + } + return new Response("ok", { headers: out }); +}); diff --git a/tools/perf_research/fetch/servers/node_server.mjs b/tools/perf_research/fetch/servers/node_server.mjs new file mode 100644 index 00000000000000..cf738ba2b0e05d --- /dev/null +++ b/tools/perf_research/fetch/servers/node_server.mjs @@ -0,0 +1,51 @@ +// HTTP server using Node's built-in fetch-compatible primitives via node:http. +// Uses standard node:http for parity with Deno.serve (both are native HTTP servers +// without an undici-style Request/Response wrapper). Run with Node 22+. +import http from "node:http"; + +const port = Number( + process.argv.find((a) => a.startsWith("--port="))?.slice(7), +) || 8081; + +const helloBytes = Buffer.from(JSON.stringify({ ok: true, msg: "hello" })); + +const server = http.createServer(async (req, res) => { + switch (req.url) { + case "/hello": { + res.setHeader("content-type", "application/json"); + res.end(helloBytes); + return; + } + case "/echo": { + const chunks = []; + for await (const c of req) chunks.push(c); + const body = JSON.parse(Buffer.concat(chunks).toString("utf8")); + res.setHeader("content-type", "application/json"); + res.end(JSON.stringify(body)); + return; + } + case "/echo-bytes": { + const chunks = []; + for await (const c of req) chunks.push(c); + res.setHeader("content-type", "application/octet-stream"); + res.end(Buffer.concat(chunks)); + return; + } + case "/headers": { + let count = 0; + for (const _ of Object.entries(req.headers)) count++; + res.end(`${count}`); + return; + } + case "/bigbody": { + const buf = Buffer.alloc(1 << 20, 0x41); + res.setHeader("content-type", "application/octet-stream"); + res.end(buf); + return; + } + default: + res.statusCode = 404; + res.end("not found"); + } +}); +server.listen(port); diff --git a/tools/perf_research/fetch/versions.txt b/tools/perf_research/fetch/versions.txt new file mode 100644 index 00000000000000..61285c83deefd3 --- /dev/null +++ b/tools/perf_research/fetch/versions.txt @@ -0,0 +1,3 @@ +deno=deno 2.7.14 (stable, release, x86_64-unknown-linux-gnu) +node=v22.22.2 +bun=1.3.14