Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions tools/perf_research/url/README.md
Original file line number Diff line number Diff line change
@@ -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.
94 changes: 94 additions & 0 deletions tools/perf_research/url/micro/url_micro.js
Original file line number Diff line number Diff line change
@@ -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();
});
42 changes: 42 additions & 0 deletions tools/perf_research/url/micro_results.jsonl
Original file line number Diff line number Diff line change
@@ -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"}
115 changes: 115 additions & 0 deletions tools/perf_research/url/profiles/url_micro.prof.txt
Original file line number Diff line number Diff line change
@@ -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: *<anonymous> 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: *<anonymous> 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: +<anonymous> 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: ~<anonymous> 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: ~<anonymous> 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: *<anonymous> ext:deno_webidl/00_webidl.js:1101:10
28 100.0% Script: ~<anonymous> 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: ~<anonymous> 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: *<anonymous> ext:deno_webidl/00_webidl.js:1101:10
3 100.0% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
3 100.0% Builtin: GeneratorPrototypeNext
1 20.0% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
1 100.0% Builtin: GeneratorPrototypeNext
1 20.0% JS: +<anonymous> ext:deno_webidl/00_webidl.js:1101:10
1 100.0% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
1 100.0% Builtin: GeneratorPrototypeNext
1 1.8% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
1 100.0% Builtin: GeneratorPrototypeNext
1 1.8% JS: +<anonymous> ext:deno_webidl/00_webidl.js:1101:10
1 100.0% Script: ~<anonymous> 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: ~<anonymous> 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: ~<anonymous> 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: ~<anonymous> 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: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
24 100.0% Builtin: GeneratorPrototypeNext
9 25.7% JS: *<anonymous> ext:deno_webidl/00_webidl.js:1101:10
9 100.0% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
9 100.0% Builtin: GeneratorPrototypeNext
1 2.9% JS: ^<anonymous> ext:deno_webidl/00_webidl.js:1101:10
1 100.0% JS: ^<anonymous> 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: ^<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:81:28
1 100.0% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
1 2.9% JS: +<anonymous> ext:deno_webidl/00_webidl.js:1101:10
1 100.0% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
1 100.0% Builtin: GeneratorPrototypeNext
16 1.0% JS: *<anonymous> ext:deno_webidl/00_webidl.js:1101:10
16 100.0% Script: ~<anonymous> file:///var/agent-loop/repo/tools/perf_research/url/micro/url_micro.js:1:1
16 100.0% Builtin: GeneratorPrototypeNext
Binary file not shown.
3 changes: 3 additions & 0 deletions tools/perf_research/url/versions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
deno=deno 2.7.14 (stable, release, x86_64-unknown-linux-gnu)
node=v22.22.2
bun=1.3.14