diff --git a/tools/perf_research/url/README.md b/tools/perf_research/url/README.md new file mode 100644 index 00000000000000..5106ef75af3677 --- /dev/null +++ b/tools/perf_research/url/README.md @@ -0,0 +1,42 @@ +# url — perf research + +Macro-level performance research on Deno's implementation of `URL` and +`URLSearchParams`. + +Bench scripts and committed V8 prof artifacts only; the PR body is the report. + +## Layout + +``` +micro/url_micro.js URL + URLSearchParams microbench (portable across runtimes) +profiles/ committed V8 prof output (.prof.txt + .v8.log.gz) +``` + +## Pinned versions (this host) + +- Deno: built from this branch's `main` via `cargo build --release --bin deno` (deno 2.7.14, v8 14.7.173.20-rusty) +- Node: `v22.22.2` +- Bun: `1.3.14` + +## Reproduction + +```bash +cargo build --release --bin deno + +# microbenches +./target/release/deno run -A --no-prompt tools/perf_research/url/micro/url_micro.js +node tools/perf_research/url/micro/url_micro.js +bun tools/perf_research/url/micro/url_micro.js + +# V8 prof (in-process; perf_event_paranoid=3 in this container blocks `perf`/`samply`) +mkdir -p /tmp/urlprof && cd /tmp/urlprof +$DENO_BIN run -A --no-prompt --v8-flags=--prof,--no-logfile-per-isolate \ + /path/to/tools/perf_research/url/micro/url_micro.js +node --prof-process v8.log > /path/to/profiles/url_micro.prof.txt +``` + +## 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 V8 +`--prof` attribution. diff --git a/tools/perf_research/url/micro/url_micro.js b/tools/perf_research/url/micro/url_micro.js new file mode 100644 index 00000000000000..76ba805ec1604d --- /dev/null +++ b/tools/perf_research/url/micro/url_micro.js @@ -0,0 +1,94 @@ +// Microbench for URL / URLSearchParams. +// +// Run with each runtime: +// deno run -A --no-prompt micro/url_micro.js +// node micro/url_micro.js +// bun micro/url_micro.js + +const ITERS = 200_000; + +function bench(name, fn) { + for (let i = 0; i < 1000; i++) fn(i); + const t0 = performance.now(); + for (let i = 0; i < ITERS; i++) fn(i); + const t1 = performance.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: Simple URL parse +bench("url_construct_simple", () => { + new URL("https://example.com/path?x=1#y"); +}); + +// 2: URL parse with base +bench("url_construct_with_base", () => { + new URL("/path?x=1", "https://example.com/base/"); +}); + +// 3: URL parse complex query string +bench("url_construct_query", () => { + new URL( + "https://example.com/api/v1/items?sort=name&page=2&filter=active&limit=50&user=alice", + ); +}); + +// 4: URL.canParse (no allocation) +bench("url_canparse_simple", () => { + URL.canParse("https://example.com/path"); +}); + +// 5: Access href on parsed URL +const u1 = new URL("https://user:pw@example.com:8443/path/segments?a=1&b=2#frag"); +bench("url_get_href", () => { + u1.href; +}); + +// 6: Access pathname +bench("url_get_pathname", () => { + u1.pathname; +}); + +// 7: Access search +bench("url_get_search", () => { + u1.search; +}); + +// 8: Set pathname (triggers reparse) +bench("url_set_pathname", () => { + u1.pathname = "/new/path"; +}); + +// 9: Set search +bench("url_set_search", () => { + u1.search = "?new=query"; +}); + +// 10: Get searchParams (each call lazily attaches) +const u2 = new URL("https://example.com/?a=1&b=2&c=3"); +bench("url_searchparams_get", () => { + u2.searchParams.get("a"); +}); + +// 11: URLSearchParams construct from string +bench("usp_construct_string", () => { + new URLSearchParams("a=1&b=2&c=3&d=4&e=5&f=6&g=7&h=8"); +}); + +// 12: URLSearchParams construct from object +const initObj = { a: "1", b: "2", c: "3", d: "4", e: "5", f: "6" }; +bench("usp_construct_obj", () => { + new URLSearchParams(initObj); +}); + +// 13: URLSearchParams get +const usp = new URLSearchParams("a=1&b=2&c=3&d=4&e=5&f=6&g=7&h=8"); +bench("usp_get", () => { + usp.get("c"); +}); + +// 14: URLSearchParams toString +bench("usp_tostring", () => { + usp.toString(); +}); diff --git a/tools/perf_research/url/micro_results.jsonl b/tools/perf_research/url/micro_results.jsonl new file mode 100644 index 00000000000000..346cacaea3f090 --- /dev/null +++ b/tools/perf_research/url/micro_results.jsonl @@ -0,0 +1,42 @@ +{"name": "url_construct_simple", "ms": "87.30", "ns_per_op": 436.5, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_construct_with_base", "ms": "154.02", "ns_per_op": 770.1, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_construct_query", "ms": "123.63", "ns_per_op": 618.1, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_canparse_simple", "ms": "64.90", "ns_per_op": 324.5, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_get_href", "ms": "1.79", "ns_per_op": 8.9, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_get_pathname", "ms": "2.52", "ns_per_op": 12.6, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_get_search", "ms": "2.77", "ns_per_op": 13.8, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_set_pathname", "ms": "153.81", "ns_per_op": 769.0, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_set_search", "ms": "148.28", "ns_per_op": 741.4, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_searchparams_get", "ms": "7.23", "ns_per_op": 36.1, "runtime": "deno", "script": "url_micro.js"} +{"name": "usp_construct_string", "ms": "296.64", "ns_per_op": 1483.2, "runtime": "deno", "script": "url_micro.js"} +{"name": "usp_construct_obj", "ms": "200.24", "ns_per_op": 1001.2, "runtime": "deno", "script": "url_micro.js"} +{"name": "usp_get", "ms": "8.35", "ns_per_op": 41.8, "runtime": "deno", "script": "url_micro.js"} +{"name": "usp_tostring", "ms": "398.08", "ns_per_op": 1990.4, "runtime": "deno", "script": "url_micro.js"} +{"name": "url_construct_simple", "ms": "77.06", "ns_per_op": 385.3, "runtime": "node", "script": "url_micro.js"} +{"name": "url_construct_with_base", "ms": "124.01", "ns_per_op": 620.0, "runtime": "node", "script": "url_micro.js"} +{"name": "url_construct_query", "ms": "82.35", "ns_per_op": 411.7, "runtime": "node", "script": "url_micro.js"} +{"name": "url_canparse_simple", "ms": "28.94", "ns_per_op": 144.7, "runtime": "node", "script": "url_micro.js"} +{"name": "url_get_href", "ms": "1.61", "ns_per_op": 8.1, "runtime": "node", "script": "url_micro.js"} +{"name": "url_get_pathname", "ms": "2.32", "ns_per_op": 11.6, "runtime": "node", "script": "url_micro.js"} +{"name": "url_get_search", "ms": "3.33", "ns_per_op": 16.7, "runtime": "node", "script": "url_micro.js"} +{"name": "url_set_pathname", "ms": "138.87", "ns_per_op": 694.4, "runtime": "node", "script": "url_micro.js"} +{"name": "url_set_search", "ms": "131.47", "ns_per_op": 657.3, "runtime": "node", "script": "url_micro.js"} +{"name": "url_searchparams_get", "ms": "3.31", "ns_per_op": 16.5, "runtime": "node", "script": "url_micro.js"} +{"name": "usp_construct_string", "ms": "43.75", "ns_per_op": 218.8, "runtime": "node", "script": "url_micro.js"} +{"name": "usp_construct_obj", "ms": "189.14", "ns_per_op": 945.7, "runtime": "node", "script": "url_micro.js"} +{"name": "usp_get", "ms": "3.41", "ns_per_op": 17.1, "runtime": "node", "script": "url_micro.js"} +{"name": "usp_tostring", "ms": "70.17", "ns_per_op": 350.8, "runtime": "node", "script": "url_micro.js"} +{"name": "url_construct_simple", "ms": "100.12", "ns_per_op": 500.6, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_construct_with_base", "ms": "169.51", "ns_per_op": 847.5, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_construct_query", "ms": "123.24", "ns_per_op": 616.2, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_canparse_simple", "ms": "48.65", "ns_per_op": 243.2, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_get_href", "ms": "3.42", "ns_per_op": 17.1, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_get_pathname", "ms": "10.65", "ns_per_op": 53.2, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_get_search", "ms": "11.51", "ns_per_op": 57.6, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_set_pathname", "ms": "168.17", "ns_per_op": 840.8, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_set_search", "ms": "167.95", "ns_per_op": 839.8, "runtime": "bun", "script": "url_micro.js"} +{"name": "url_searchparams_get", "ms": "4.02", "ns_per_op": 20.1, "runtime": "bun", "script": "url_micro.js"} +{"name": "usp_construct_string", "ms": "351.55", "ns_per_op": 1757.8, "runtime": "bun", "script": "url_micro.js"} +{"name": "usp_construct_obj", "ms": "138.66", "ns_per_op": 693.3, "runtime": "bun", "script": "url_micro.js"} +{"name": "usp_get", "ms": "4.53", "ns_per_op": 22.6, "runtime": "bun", "script": "url_micro.js"} +{"name": "usp_tostring", "ms": "103.54", "ns_per_op": 517.7, "runtime": "bun", "script": "url_micro.js"} diff --git a/tools/perf_research/url/profiles/url_micro.prof.txt b/tools/perf_research/url/profiles/url_micro.prof.txt new file mode 100644 index 00000000000000..5d5d748327d283 --- /dev/null +++ b/tools/perf_research/url/profiles/url_micro.prof.txt @@ -0,0 +1,115 @@ +Testing v8 version different from logging version +(node:76267) 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_url/v8.log, (1559 ticks, 44 unaccounted, 0 excluded). + [Shared libraries]: + ticks total nonlib name + 1209 77.5% /var/agent-loop/repo/target/release/deno + 111 7.1% /usr/lib/x86_64-linux-gnu/libc.so.6 + [JavaScript]: + ticks total nonlib name + 35 2.2% 14.6% Builtin: LoadIC + 16 1.0% 6.7% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 11 0.7% 4.6% Builtin: StringPrototypeToWellFormed + 11 0.7% 4.6% Builtin: ObjectPrototypeHasOwnProperty + 8 0.5% 3.3% Builtin: KeyedLoadIC_Megamorphic + 8 0.5% 3.3% Builtin: ArrayMap + 7 0.4% 2.9% Builtin: CallFunction_ReceiverIsNullOrUndefined + 6 0.4% 2.5% Builtin: ObjectHasOwn + 6 0.4% 2.5% Builtin: KeyedStoreIC_Megamorphic + 5 0.3% 2.1% JS: * ext:deno_web/00_url.js:159:9 + 5 0.3% 2.1% Builtin: CreateShallowObjectLiteral + 5 0.3% 2.1% Builtin: Call_ReceiverIsNullOrUndefined + 4 0.3% 1.7% Builtin: CallFunction_ReceiverIsAny + 4 0.3% 1.7% Builtin: CEntry_Return1_ArgvOnStack_NoBuiltinExit + 3 0.2% 1.3% Builtin: RecordWriteSaveFP + 3 0.2% 1.3% Builtin: ObjectKeys + 2 0.1% 0.8% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 2 0.1% 0.8% Builtin: KeyedStoreICTrampoline_Megamorphic + 2 0.1% 0.8% Builtin: KeyedLoadIC + 2 0.1% 0.8% Builtin: ForInEnumerate + 2 0.1% 0.8% Builtin: FastNewObject + 2 0.1% 0.8% Builtin: CallApiCallbackOptimizedNoProfiling + 1 0.1% 0.4% JS: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:21:31 + 1 0.1% 0.4% Builtin: Typeof + 1 0.1% 0.4% Builtin: StringPrototypeSlice + 1 0.1% 0.4% Builtin: LoadICTrampoline + 1 0.1% 0.4% Builtin: KeyedLoadICTrampoline_Megamorphic + 1 0.1% 0.4% Builtin: Call_ReceiverIsAny + 1 0.1% 0.4% Builtin: ArrayFilter + [C++]: + ticks total nonlib name + 14 0.9% 5.9% __libc_malloc@@GLIBC_2.2.5 + 11 0.7% 4.6% __libc_free@@GLIBC_2.2.5 + 7 0.4% 2.9% syscall@@GLIBC_2.2.5 + 4 0.3% 1.7% __libc_realloc@@GLIBC_2.2.5 + 2 0.1% 0.8% __getpid@@GLIBC_2.2.5 + 1 0.1% 0.4% __mmap@@GLIBC_PRIVATE + [Summary]: + ticks total nonlib name + 156 10.0% 65.3% JavaScript + 39 2.5% 16.3% C++ + 42 2.7% 17.6% GC + 1320 84.7% Shared libraries + 44 2.8% Unaccounted + [C++ entry points]: + ticks cpp total name + 14 46.7% 0.9% __libc_malloc@@GLIBC_2.2.5 + 11 36.7% 0.7% __libc_free@@GLIBC_2.2.5 + 4 13.3% 0.3% __libc_realloc@@GLIBC_2.2.5 + 1 3.3% 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 + 1209 77.5% /var/agent-loop/repo/target/release/deno + 1106 91.5% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1106 100.0% Builtin: GeneratorPrototypeNext + 56 4.6% /var/agent-loop/repo/target/release/deno + 28 50.0% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 28 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 28 100.0% Builtin: GeneratorPrototypeNext + 21 37.5% Builtin: ObjectKeys + 21 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 21 100.0% Builtin: GeneratorPrototypeNext + 5 8.9% Builtin: StringPrototypeToWellFormed + 3 60.0% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 3 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 3 100.0% Builtin: GeneratorPrototypeNext + 1 20.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1 100.0% Builtin: GeneratorPrototypeNext + 1 20.0% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1 100.0% Builtin: GeneratorPrototypeNext + 1 1.8% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1 100.0% Builtin: GeneratorPrototypeNext + 1 1.8% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1 100.0% Builtin: GeneratorPrototypeNext + 111 7.1% /usr/lib/x86_64-linux-gnu/libc.so.6 + 104 93.7% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 104 100.0% Builtin: GeneratorPrototypeNext + 44 2.8% UNKNOWN + 42 95.5% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 42 100.0% Builtin: GeneratorPrototypeNext + 1 2.3% JS: ^bench file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:10:15 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1 100.0% Builtin: GeneratorPrototypeNext + 35 2.2% Builtin: LoadIC + 24 68.6% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 24 100.0% Builtin: GeneratorPrototypeNext + 9 25.7% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 9 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 9 100.0% Builtin: GeneratorPrototypeNext + 1 2.9% JS: ^ ext:deno_webidl/00_webidl.js:1101:10 + 1 100.0% JS: ^ ext:deno_web/00_url.js:889:7 + 1 100.0% JS: ^URLSearchParams ext:deno_web/00_url.js:120:14 + 1 100.0% JS: ^ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:81:28 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1 2.9% JS: + ext:deno_webidl/00_webidl.js:1101:10 + 1 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 1 100.0% Builtin: GeneratorPrototypeNext + 16 1.0% JS: * ext:deno_webidl/00_webidl.js:1101:10 + 16 100.0% Script: ~ file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1 + 16 100.0% Builtin: GeneratorPrototypeNext diff --git a/tools/perf_research/url/profiles/url_micro.v8.log.gz b/tools/perf_research/url/profiles/url_micro.v8.log.gz new file mode 100644 index 00000000000000..dd9834eb6c640c Binary files /dev/null and b/tools/perf_research/url/profiles/url_micro.v8.log.gz differ diff --git a/tools/perf_research/url/versions.txt b/tools/perf_research/url/versions.txt new file mode 100644 index 00000000000000..61285c83deefd3 --- /dev/null +++ b/tools/perf_research/url/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