What happened
At measured baseline 9495bfc, repeatedly trimming two whitespace characters from each edge scales with the full interior length in Perry while Node stays nearly constant. At n=1,000,000, the 16-trim workload is approximately 11,890x slower for ASCII and 143,940x for Unicode. The Unicode figure includes an additional indexing/checksum cost described below. Reproduce on current main first: the published measurements concern the pinned baseline, and current main has not been measured.
Measured against Node v26.5.1 using Perry perry 0.5.1531 at 9495bfc95e2afcfb5a7cb535e440e61ec0722cb1. This is evidence from that pinned revision, not a claim that current main was remeasured. First reproduce on current main; if it is already fixed, identify the fixing commit and attach the comparison.
Measurements
Times are median milliseconds per workload invocation. Ratios are Perry/Node. A correctness or timeout classification takes precedence over performance; successful smaller-size timings on those rows are diagnostic evidence.
string-trim-ascii — ASYMPTOTIC
| n |
Node ms / status |
Perry ms / status |
ratio |
Node checksum |
Perry checksum |
| 100 |
0.015099 |
0.030885 |
2.05× |
3299494000 |
3299494000 |
| 1000 |
0.013687 |
0.182005 |
13.30× |
3920510048 |
3920510048 |
| 10000 |
0.014236 |
1.867674 |
131.19× |
14059261744 |
14059261744 |
| 100000 |
0.015131 |
18.454479 |
1219.62× |
10385466752 |
10385466752 |
| 1000000 |
0.015101 |
179.545708 |
11889.88× |
4515886736 |
4515886736 |
Log(time)/log(n) least-squares slopes: Perry 0.953, Node 0.004, delta 0.949.
Workload: ascii variant. n counts repeated input tokens, not bytes; UTF-16 length and UTF-8 byte length differ for Unicode. String result hashes sample roughly 32 positions for long strings (at most 63 for short strings), plus length. Only two whitespace characters at each edge; 16 trims per run expose interior-size scaling on reused immutable input. Perry source establishes O(n) scanning plus copying even though the trimmed edges are constant-sized. V8 internals were not inspected; its substring-sharing explanation remains a hypothesis.
Interpretation note: Both fitted slopes are below one; fixed overhead or allocation thresholds can influence this finite-range classification.
string-trim-unicode — ASYMPTOTIC
| n |
Node ms / status |
Perry ms / status |
ratio |
Node checksum |
Perry checksum |
| 100 |
0.015021 |
0.241439 |
16.07× |
15682954032 |
15682954032 |
| 1000 |
0.013979 |
2.272764 |
162.58× |
14356907120 |
14356907120 |
| 10000 |
0.014529 |
22.364542 |
1539.27× |
7400533984 |
7400533984 |
| 100000 |
0.015639 |
210.658917 |
13470.45× |
11699376128 |
11699376128 |
| 1000000 |
0.014880 |
2141.890875 |
143939.73× |
8362401136 |
8362401136 |
Log(time)/log(n) least-squares slopes: Perry 0.986, Node 0.004, delta 0.982.
Workload: unicode variant. n counts repeated input tokens, not bytes; UTF-16 length and UTF-8 byte length differ for Unicode. String result hashes sample roughly 32 positions for long strings (at most 63 for short strings), plus length. Only two whitespace characters at each edge; 16 trims per run expose interior-size scaling on reused immutable input. Perry source establishes O(n) scanning plus copying even though the trimmed edges are constant-sized. V8 internals were not inspected; its substring-sharing explanation remains a hypothesis. The timed Unicode checksum adds roughly 32 prefix scans per trimmed result, O(n) total per trim; its cost is included in the ratio.
Interpretation note: Both fitted slopes are below one; fixed overhead or allocation thresholds can influence this finite-range classification.
What is expected / acceptance criteria
Implementation to inspect
Hypothesis: Source-grounded hypothesis at 9495bfc: crates/perry-runtime/src/string/slice_ops.rs::js_whitespace_trim_range (around line 240) finds the trailing edge by walking forward through every interior character. trim_impl (around line 277) then calls js_string_from_bytes on the retained range, allocating and copying the complete result. Thus fixed-size whitespace removal still incurs O(n) scanning and copying on every call. The source directly supports this Perry cost. Node's flat measured curve is consistent with boundary scanning and shared substring storage, but the precise V8 mechanism is an inference; V8 implementation source was not inspected.
The timed region includes 16 trim operations and their result checksums. In the Unicode companion, hashString reads roughly 32 positions per result; js_string_char_code_at/utf16_unit_at in string/char_ops.rs scans from the beginning for each non-ASCII index. That adds O(n) total checksum work per trimmed result and can dominate the Unicode ratio. The ASCII companion isolates the trim scaling problem more clearly because its sampled indexed reads have constant cost. Input is immutable and reused after setup.
No matching open performance issue was found in the reviewed issue list. Open #8549 concerns an emitted-IR receiver-evaluation assertion for make().trim(), not interior-length scanning. Closed #6085 is relevant safety history: preserve bounded reads on exact-sized and malformed WTF-8 payloads when changing the trimming scanner.
Source reading narrows the investigation; it does not establish exclusive runtime/compiler attribution. No compiler or runtime changes were made to obtain these measurements.
Agent scope and coordination
Primary files are string/slice_ops.rs and any string allocation/view representation helpers needed for retained substrings. Coordinate shared representation work with the Unicode-slice correctness issue. The Unicode-indexing issue shares string/char_ops.rs through checksum consumption; trim's ASCII scanning/copying work can be investigated independently, and a trim change should not claim the indexing improvement as its own.
Implementation work can proceed in separate branches. Serialize benchmark runs on any shared host; parallel timing runs invalidate small performance comparisons. Preserve language semantics and moving-GC safety.
Reproduce and remeasure
Everything needed for the workload is embedded below; no private repository, fixture, npm package, or shared prelude is required. Save a complete benchmark block under its indicated filename in /tmp/perry-builtin-repro/. Use Node 26.5.1 to match this baseline; it runs these TypeScript files directly.
From the Perry checkout/branch being evaluated:
mkdir -p /tmp/perry-builtin-repro
cargo build --release --locked -p perry -p perry-runtime-static -p perry-stdlib-static
export PERRY_RUNTIME_DIR="$PWD/target/release"
export TZ=UTC LC_ALL=en_US.UTF-8
git rev-parse HEAD
node --version
target/release/perry compile /tmp/perry-builtin-repro/string-trim-ascii.ts --no-auto-optimize -o /tmp/perry-builtin-repro/app
To reproduce the historical baseline, use the pinned commit above in a separate checkout and build the compiler and both libraries there. Repeat compilation for each additional benchmark below. Run this small driver from the same checkout, changing name and sizes for that benchmark:
import json, math, subprocess
name = 'string-trim-ascii'
sizes = [100, 1000, 10000, 100000, 1000000]
result_on_stderr = False
stopped = set()
points = {"node": [], "perry": []}
for n in sizes:
pair = {}
for engine, cmd in [("node", ["node", f"/tmp/perry-builtin-repro/{name}.ts"]),
("perry", ["/tmp/perry-builtin-repro/app"])]:
if engine in stopped: continue
try:
p = subprocess.run(cmd + [str(n)], capture_output=True, text=True, timeout=60)
except subprocess.TimeoutExpired:
print(engine, n, "TIMEOUT"); stopped.add(engine); continue
if p.returncode:
print(engine, n, "ERROR", p.returncode, p.stderr, p.stdout); continue
r = json.loads(p.stderr if result_on_stderr else p.stdout)
pair[engine] = r
points[engine].append((n, r["ms_per_run"]))
print(engine, r)
if len(pair) == 2:
print("ratio", n, pair["perry"]["ms_per_run"] / pair["node"]["ms_per_run"],
"checksum_match", pair["perry"]["checksum"] == pair["node"]["checksum"])
def slope(rows):
if len(rows) < 2: return None
x = [math.log(n) for n, t in rows]; y = [math.log(t) for n, t in rows]
mx = sum(x)/len(x); my = sum(y)/len(y)
return sum((a-mx)*(b-my) for a,b in zip(x,y))/sum((a-mx)**2 for a in x)
print("slopes", {engine: slope(rows) for engine, rows in points.items()})
Record before/after results from the same unchanged source, engine versions and host. The measured driver uses seeded setup outside timers, at least 200 ms AND five warmup runs, then seven samples with at least 20 ms measured work each. Fresh input is prepared before each timer for mutating workloads. The median per-run time is reported, with checksum consistency checked on every invocation. Timeouts cover setup, warmup and sampling, not just one builtin call.
Environment and limits
- CPU: Apple M1 Max; 10 logical cores; arm64.
- OS:
macOS-26.5-arm64-arm-64bit-Mach-O; target: native host.
- Node:
v26.5.1; Perry: perry 0.5.1531; build: release from source.
- Compile flag:
--no-auto-optimize; compiler and both matching runtime archives were rebuilt together.
- The pinned source revision and compiler/runtime/Node artifact hashes were unchanged throughout the sweep.
- Load average at measurement start:
[58.3896484375, 53.017578125, 57.90087890625]; end: [25.240234375, 30.416015625, 24.8681640625].
- Host contention limits precise constant-factor claims; repeat on a quiet host before asserting an improvement.
- Timings include timer overhead and checksum calculation. String hashes bound lookup count, not Unicode lookup cost; indexed consumption may also force Node string materialization.
Minimal correctness reductions
This issue is a performance workload; the complete checksum-gated reproducer follows.
Complete standalone benchmark sources
string-trim-ascii.ts — sizes [100, 1000, 10000, 100000, 1000000]
Size meanings and fresh-input policy are in the leading metadata. result_on_stderr for this file: False.
// @runtime {"name": "string-trim-ascii", "category": "strings", "verification": "checksum", "sources": [{"file": "crates/perry-runtime/src/string/slice_ops.rs", "function": "js_string_trim"}, {"file": "crates/perry-runtime/src/string/slice_ops.rs", "function": "js_whitespace_trim_range"}, {"file": "crates/perry-runtime/src/string/slice_ops.rs", "function": "trim_impl"}], "hypothesis": "Hypothesis: js_whitespace_trim_range scans the whole interior forward to find the final non-whitespace sequence, then trim_impl allocates/copies the full result. Nearly constant Node timings are consistent with boundary scanning and shared substring storage, but that V8 mechanism is an inference.", "notes": "ascii variant. n counts repeated input tokens, not bytes; UTF-16 length and UTF-8 byte length differ for Unicode. String result hashes sample roughly 32 positions for long strings (at most 63 for short strings), plus length. Only two whitespace characters at each edge; 16 trims per run expose interior-size scaling on reused immutable input. Perry source establishes O(n) scanning plus copying even though the trimmed edges are constant-sized. V8 internals were not inspected; its substring-sharing explanation remains a hypothesis.", "asynchronous": false, "output_stderr": false, "fresh_input": false}
// Standalone file. Shared helpers/driver are inlined by common.py.
let seed = 0x12345678;
function rnd(): number {
seed ^= seed << 13; seed ^= seed >>> 17; seed ^= seed << 5;
return (seed >>> 0) / 4294967296;
}
function numbers(n: number): number[] {
const a: number[] = [];
for (let i = 0; i < n; i++) a.push(Math.floor(rnd() * 1000000));
return a;
}
function hashArray(a: number[]): number {
let h = a.length;
for (let i = 0; i < a.length; i++) h = (h * 31 + a[i]) % 1000000007;
return h;
}
// Bounded checksum work avoids making string slicing/indexing part of every
// string benchmark's asymptotic cost. The workload itself consumes its result.
function hashString(s: string): number {
let h = s.length;
const step = Math.max(1, Math.floor(s.length / 32));
for (let i = 0; i < s.length; i += step) h = (h * 31 + s.charCodeAt(i)) % 1000000007;
return h;
}
function setup(n: number): string { return ' \t' + "aBcD".repeat(n) + '\n '; }
function run(input: string): number {
let h = 0;
for (let i = 0; i < 16; i++) h += hashString(input.trim());
return h;
}
// Size is the final argument: both native Perry and Node expose it reliably.
const n = Number(process.argv[process.argv.length - 1]);
if (!(n > 0)) throw new Error("Expected a positive size argument");
function benchmarkMain(): void {
seed = 0x12345678;
const preparedInput = setup(n);
let checksum = 0;
let seen = false;
let warmMs = 0;
let warmRuns = 0;
while (warmMs < 200 || warmRuns < 5) {
seed = 0x12345678;
const input = preparedInput;
const start = performance.now();
const value = run(input);
const elapsed = performance.now() - start;
if (!(elapsed >= 0)) throw new Error("Invalid monotonic timer");
warmMs += elapsed;
warmRuns++;
if (seen && value !== checksum) throw new Error("CORRECTNESS: unstable checksum during warmup");
checksum = value;
seen = true;
}
const samples: number[] = [];
let runs = 0;
for (let sample = 0; sample < 7; sample++) {
let elapsed = 0;
let count = 0;
// Mutable workloads prepare fresh input BEFORE each timer; immutable
// workloads reuse setup. Neither preparation nor validation is measured.
while (elapsed < 20) {
seed = 0x12345678;
const input = preparedInput;
const start = performance.now();
const value = run(input);
const duration = performance.now() - start;
if (!(duration >= 0)) throw new Error("Invalid monotonic timer");
elapsed += duration;
count++;
if (value !== checksum) throw new Error("CORRECTNESS: unstable checksum during sampling");
}
samples.push(elapsed / count);
runs += count;
}
// Do not depend on Array.sort to compute the median of a sort benchmark.
for (let i = 1; i < samples.length; i++) {
const v = samples[i];
let j = i - 1;
while (j >= 0 && samples[j] > v) { samples[j + 1] = samples[j]; j--; }
samples[j + 1] = v;
}
console.log(JSON.stringify({name: "string-trim-ascii", category: "strings", n,
ms_per_run: samples[3], runs, checksum}));
}
benchmarkMain();
string-trim-unicode.ts — sizes [100, 1000, 10000, 100000, 1000000]
Size meanings and fresh-input policy are in the leading metadata. result_on_stderr for this file: False.
// @runtime {"name": "string-trim-unicode", "category": "strings", "verification": "checksum", "sources": [{"file": "crates/perry-runtime/src/string/slice_ops.rs", "function": "js_string_trim"}, {"file": "crates/perry-runtime/src/string/slice_ops.rs", "function": "js_whitespace_trim_range"}, {"file": "crates/perry-runtime/src/string/slice_ops.rs", "function": "trim_impl"}, {"file": "crates/perry-runtime/src/string/char_ops.rs", "function": "js_string_char_code_at"}, {"file": "crates/perry-runtime/src/string/char_ops.rs", "function": "utf16_unit_at"}], "hypothesis": "Hypothesis: js_whitespace_trim_range scans the whole interior forward to find the final non-whitespace sequence, then trim_impl allocates/copies the full result. Nearly constant Node timings are consistent with boundary scanning and shared substring storage, but that V8 mechanism is an inference. Unicode result hashing adds repeated UTF-16 prefix scans.", "notes": "unicode variant. n counts repeated input tokens, not bytes; UTF-16 length and UTF-8 byte length differ for Unicode. String result hashes sample roughly 32 positions for long strings (at most 63 for short strings), plus length. Only two whitespace characters at each edge; 16 trims per run expose interior-size scaling on reused immutable input. Perry source establishes O(n) scanning plus copying even though the trimmed edges are constant-sized. V8 internals were not inspected; its substring-sharing explanation remains a hypothesis. The timed Unicode checksum adds roughly 32 prefix scans per trimmed result, O(n) total per trim; its cost is included in the ratio.", "asynchronous": false, "output_stderr": false, "fresh_input": false}
// Standalone file. Shared helpers/driver are inlined by common.py.
let seed = 0x12345678;
function rnd(): number {
seed ^= seed << 13; seed ^= seed >>> 17; seed ^= seed << 5;
return (seed >>> 0) / 4294967296;
}
function numbers(n: number): number[] {
const a: number[] = [];
for (let i = 0; i < n; i++) a.push(Math.floor(rnd() * 1000000));
return a;
}
function hashArray(a: number[]): number {
let h = a.length;
for (let i = 0; i < a.length; i++) h = (h * 31 + a[i]) % 1000000007;
return h;
}
// Bounded checksum work avoids making string slicing/indexing part of every
// string benchmark's asymptotic cost. The workload itself consumes its result.
function hashString(s: string): number {
let h = s.length;
const step = Math.max(1, Math.floor(s.length / 32));
for (let i = 0; i < s.length; i += step) h = (h * 31 + s.charCodeAt(i)) % 1000000007;
return h;
}
function setup(n: number): string { return ' \t' + "ä中😀Ö".repeat(n) + '\n '; }
function run(input: string): number {
let h = 0;
for (let i = 0; i < 16; i++) h += hashString(input.trim());
return h;
}
// Size is the final argument: both native Perry and Node expose it reliably.
const n = Number(process.argv[process.argv.length - 1]);
if (!(n > 0)) throw new Error("Expected a positive size argument");
function benchmarkMain(): void {
seed = 0x12345678;
const preparedInput = setup(n);
let checksum = 0;
let seen = false;
let warmMs = 0;
let warmRuns = 0;
while (warmMs < 200 || warmRuns < 5) {
seed = 0x12345678;
const input = preparedInput;
const start = performance.now();
const value = run(input);
const elapsed = performance.now() - start;
if (!(elapsed >= 0)) throw new Error("Invalid monotonic timer");
warmMs += elapsed;
warmRuns++;
if (seen && value !== checksum) throw new Error("CORRECTNESS: unstable checksum during warmup");
checksum = value;
seen = true;
}
const samples: number[] = [];
let runs = 0;
for (let sample = 0; sample < 7; sample++) {
let elapsed = 0;
let count = 0;
// Mutable workloads prepare fresh input BEFORE each timer; immutable
// workloads reuse setup. Neither preparation nor validation is measured.
while (elapsed < 20) {
seed = 0x12345678;
const input = preparedInput;
const start = performance.now();
const value = run(input);
const duration = performance.now() - start;
if (!(duration >= 0)) throw new Error("Invalid monotonic timer");
elapsed += duration;
count++;
if (value !== checksum) throw new Error("CORRECTNESS: unstable checksum during sampling");
}
samples.push(elapsed / count);
runs += count;
}
// Do not depend on Array.sort to compute the median of a sort benchmark.
for (let i = 1; i < samples.length; i++) {
const v = samples[i];
let j = i - 1;
while (j >= 0 && samples[j] > v) { samples[j + 1] = samples[j]; j--; }
samples[j + 1] = v;
}
console.log(JSON.stringify({name: "string-trim-unicode", category: "strings", n,
ms_per_run: samples[3], runs, checksum}));
}
benchmarkMain();
What happened
At measured baseline 9495bfc, repeatedly trimming two whitespace characters from each edge scales with the full interior length in Perry while Node stays nearly constant. At n=1,000,000, the 16-trim workload is approximately 11,890x slower for ASCII and 143,940x for Unicode. The Unicode figure includes an additional indexing/checksum cost described below. Reproduce on current main first: the published measurements concern the pinned baseline, and current main has not been measured.
Measured against Node
v26.5.1using Perryperry 0.5.1531at9495bfc95e2afcfb5a7cb535e440e61ec0722cb1. This is evidence from that pinned revision, not a claim that current main was remeasured. First reproduce on current main; if it is already fixed, identify the fixing commit and attach the comparison.Measurements
Times are median milliseconds per workload invocation. Ratios are Perry/Node. A correctness or timeout classification takes precedence over performance; successful smaller-size timings on those rows are diagnostic evidence.
string-trim-ascii— ASYMPTOTICLog(time)/log(n) least-squares slopes: Perry 0.953, Node 0.004, delta 0.949.
Workload: ascii variant. n counts repeated input tokens, not bytes; UTF-16 length and UTF-8 byte length differ for Unicode. String result hashes sample roughly 32 positions for long strings (at most 63 for short strings), plus length. Only two whitespace characters at each edge; 16 trims per run expose interior-size scaling on reused immutable input. Perry source establishes O(n) scanning plus copying even though the trimmed edges are constant-sized. V8 internals were not inspected; its substring-sharing explanation remains a hypothesis.
Interpretation note: Both fitted slopes are below one; fixed overhead or allocation thresholds can influence this finite-range classification.
string-trim-unicode— ASYMPTOTICLog(time)/log(n) least-squares slopes: Perry 0.986, Node 0.004, delta 0.982.
Workload: unicode variant. n counts repeated input tokens, not bytes; UTF-16 length and UTF-8 byte length differ for Unicode. String result hashes sample roughly 32 positions for long strings (at most 63 for short strings), plus length. Only two whitespace characters at each edge; 16 trims per run expose interior-size scaling on reused immutable input. Perry source establishes O(n) scanning plus copying even though the trimmed edges are constant-sized. V8 internals were not inspected; its substring-sharing explanation remains a hypothesis. The timed Unicode checksum adds roughly 32 prefix scans per trimmed result, O(n) total per trim; its cost is included in the ratio.
Interpretation note: Both fitted slopes are below one; fixed overhead or allocation thresholds can influence this finite-range classification.
What is expected / acceptance criteria
Implementation to inspect
Hypothesis: Source-grounded hypothesis at 9495bfc: crates/perry-runtime/src/string/slice_ops.rs::js_whitespace_trim_range (around line 240) finds the trailing edge by walking forward through every interior character. trim_impl (around line 277) then calls js_string_from_bytes on the retained range, allocating and copying the complete result. Thus fixed-size whitespace removal still incurs O(n) scanning and copying on every call. The source directly supports this Perry cost. Node's flat measured curve is consistent with boundary scanning and shared substring storage, but the precise V8 mechanism is an inference; V8 implementation source was not inspected.
The timed region includes 16 trim operations and their result checksums. In the Unicode companion, hashString reads roughly 32 positions per result; js_string_char_code_at/utf16_unit_at in string/char_ops.rs scans from the beginning for each non-ASCII index. That adds O(n) total checksum work per trimmed result and can dominate the Unicode ratio. The ASCII companion isolates the trim scaling problem more clearly because its sampled indexed reads have constant cost. Input is immutable and reused after setup.
No matching open performance issue was found in the reviewed issue list. Open #8549 concerns an emitted-IR receiver-evaluation assertion for make().trim(), not interior-length scanning. Closed #6085 is relevant safety history: preserve bounded reads on exact-sized and malformed WTF-8 payloads when changing the trimming scanner.
js_string_trimjs_whitespace_trim_rangetrim_impljs_string_char_code_atutf16_unit_atSource reading narrows the investigation; it does not establish exclusive runtime/compiler attribution. No compiler or runtime changes were made to obtain these measurements.
Agent scope and coordination
Primary files are string/slice_ops.rs and any string allocation/view representation helpers needed for retained substrings. Coordinate shared representation work with the Unicode-slice correctness issue. The Unicode-indexing issue shares string/char_ops.rs through checksum consumption; trim's ASCII scanning/copying work can be investigated independently, and a trim change should not claim the indexing improvement as its own.
Implementation work can proceed in separate branches. Serialize benchmark runs on any shared host; parallel timing runs invalidate small performance comparisons. Preserve language semantics and moving-GC safety.
Coordinate with this benchmark task: runtime: string.slice splits astral characters incorrectly and makes suffix parsing quadratic #10061
Coordinate with this benchmark task: perf(string): non-ASCII charCodeAt and bracket scans become quadratic #10055
Related history/context: e2e-scoped: any_call_result_trim_emits_string_tag_dispatch has been failing on main since ~2026-08-10, reddening unrelated PRs #8549
Related history/context: Runtime split()/parseFloat() read past exact-sized slice allocations -> intermittent AV (c0000005) on hot paths #6085
Reproduce and remeasure
Everything needed for the workload is embedded below; no private repository, fixture, npm package, or shared prelude is required. Save a complete benchmark block under its indicated filename in
/tmp/perry-builtin-repro/. Use Node 26.5.1 to match this baseline; it runs these TypeScript files directly.From the Perry checkout/branch being evaluated:
To reproduce the historical baseline, use the pinned commit above in a separate checkout and build the compiler and both libraries there. Repeat compilation for each additional benchmark below. Run this small driver from the same checkout, changing
nameandsizesfor that benchmark:Record before/after results from the same unchanged source, engine versions and host. The measured driver uses seeded setup outside timers, at least 200 ms AND five warmup runs, then seven samples with at least 20 ms measured work each. Fresh input is prepared before each timer for mutating workloads. The median per-run time is reported, with checksum consistency checked on every invocation. Timeouts cover setup, warmup and sampling, not just one builtin call.
Environment and limits
macOS-26.5-arm64-arm-64bit-Mach-O; target: native host.v26.5.1; Perry:perry 0.5.1531; build: release from source.--no-auto-optimize; compiler and both matching runtime archives were rebuilt together.[58.3896484375, 53.017578125, 57.90087890625]; end:[25.240234375, 30.416015625, 24.8681640625].Minimal correctness reductions
This issue is a performance workload; the complete checksum-gated reproducer follows.
Complete standalone benchmark sources
string-trim-ascii.ts — sizes [100, 1000, 10000, 100000, 1000000]
Size meanings and fresh-input policy are in the leading metadata.
result_on_stderrfor this file:False.string-trim-unicode.ts — sizes [100, 1000, 10000, 100000, 1000000]
Size meanings and fresh-input policy are in the leading metadata.
result_on_stderrfor this file:False.