Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
22 changes: 22 additions & 0 deletions bench/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,25 @@ hyperfine --warmup 1 \
gets a fresh global; the VM-level SourceProvider cache means the 2MB is
transpiled and parsed once, and every subsequent file rebuilds the module
record from cached `module_info` with zero re-parsing.

## `--isolate`: JIT-heavy shared dependencies (global reuse)

```sh
bun app/setup.ts
hyperfine --warmup 1 \
-n 'fresh global' 'bun test --isolate ./app/suite' \
-n 'reuse global' 'BUN_FEATURE_FLAG_EXPERIMENTAL_TEST_ISOLATE_REUSE_GLOBAL=1 bun test --isolate ./app/suite' \
-n 'no isolate' 'bun test ./app/suite'
```

128 test files that each import zod, date-fns and lodash-es and call into them
~2000 times, enough for JSC to tier the hot dependency functions up into
DFG/FTL. With a fresh global per file the dependency `JSModuleRecord`s (and
the `FunctionExecutable` / `CodeBlock` chain hanging off them) are rebuilt and
re-optimized every file; the experimental reuse path keeps the same global and
evicts only project-source module entries, so dependencies keep their optimized
code.

To see the JIT-thread cost directly, compare user-CPU time (all threads) to
wall time: the gap is almost entirely DFG/FTL worklist threads recompiling the
same dependency functions.
86 changes: 86 additions & 0 deletions bench/test/app/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Generates a JIT-heavy suite: FILES test files that each import a handful of
// real-world dependencies (zod, date-fns, lodash-es) and call into them enough
// times that JSC tiers the dependency functions up into DFG/FTL. Under
// `--isolate` with a fresh global per file, every file re-links every
// dependency module and re-tiers every hot function from scratch; the
// experimental reuse-global path keeps dependency module records alive so the
// optimized CodeBlocks survive.
//
// Usage:
// bun bench/test/app/setup.ts
// hyperfine --warmup 1 \
// -n 'fresh global' 'bun test --isolate bench/test/app/suite' \
// -n 'reuse global' 'BUN_FEATURE_FLAG_EXPERIMENTAL_TEST_ISOLATE_REUSE_GLOBAL=1 bun test --isolate bench/test/app/suite'
Comment thread
robobun marked this conversation as resolved.
Resolved
//
// To see JIT compile time directly:
// BUN_JSC_reportTotalCompileTimes=1 bun test --isolate bench/test/app/suite
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
import { mkdirSync, rmSync, writeFileSync } from "node:fs";

const FILES = Number(process.env.FILES ?? 128);
const ITERS = Number(process.env.ITERS ?? 2000);
const root = import.meta.dir + "/suite";

rmSync(root, { recursive: true, force: true });
mkdirSync(root, { recursive: true });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

// Shared project-side helper: lives outside node_modules so the reuse path
// still evicts it (project modules must always be fresh).
writeFileSync(
`${root}/hot.ts`,
`import { z } from "zod";
import { format, addDays, differenceInMilliseconds, parseISO } from "date-fns";
import { chunk, sortBy, uniqBy, groupBy, sumBy } from "lodash-es";

export const schema = z.object({
id: z.number().int().positive(),
name: z.string().min(1).max(64),
email: z.string().email(),
tags: z.array(z.string()).max(8),
createdAt: z.string(),
});
export type Row = z.infer<typeof schema>;

export function hot(iters: number) {
const base = parseISO("2024-01-01T00:00:00.000Z");
const rows: Row[] = [];
for (let i = 0; i < 64; i++) {
rows.push(
schema.parse({
id: i + 1,
name: "user" + (i % 7),
email: "u" + i + "@example.com",
tags: ["t" + (i % 3), "t" + (i % 5)],
createdAt: format(addDays(base, i % 30), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"),
}),
);
}
let acc = 0;
for (let k = 0; k < iters; k++) {
const grouped = groupBy(rows, r => r.name);
const sorted = sortBy(rows, r => r.id);
const uniq = uniqBy(rows, r => r.tags[0]);
const chunks = chunk(sorted, 8);
acc += sumBy(uniq, r => r.id);
acc += chunks.length;
acc += Object.keys(grouped).length;
acc += differenceInMilliseconds(parseISO(rows[k & 63].createdAt), base) & 1023;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
return acc;
}
`,
);

for (let f = 0; f < FILES; f++) {
writeFileSync(
`${root}/t${String(f).padStart(3, "0")}.test.ts`,
`import { hot } from "./hot";
test("t${f}", () => {
const v = hot(${ITERS});
expect(typeof v).toBe("number");
expect(v > 0).toBe(true);
});
`,
);
}

console.log(`wrote ${root}/hot.ts + ${FILES} test files (ITERS=${ITERS})`);
9 changes: 9 additions & 0 deletions bench/test/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion bench/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"private": true,
"type": "module",
"devDependencies": {
"vitest": "^4.1.4"
"date-fns": "^4.1.0",
"lodash-es": "^4.17.21",
"vitest": "^4.1.4",
"zod": "^3.25.76"
Comment thread
robobun marked this conversation as resolved.
Resolved
}
}
1 change: 1 addition & 0 deletions src/bun_core/env_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub mod feature_flag {
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG, "BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER, "BUN_FEATURE_FLAG_DISABLE_ASYNC_TRANSPILER", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_ISOLATION_SOURCE_CACHE, "BUN_FEATURE_FLAG_DISABLE_ISOLATION_SOURCE_CACHE", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_EXPERIMENTAL_TEST_ISOLATE_REUSE_GLOBAL, "BUN_FEATURE_FLAG_EXPERIMENTAL_TEST_ISOLATE_REUSE_GLOBAL", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_DNS_CACHE, "BUN_FEATURE_FLAG_DISABLE_DNS_CACHE", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_DNS_CACHE_LIBINFO, "BUN_FEATURE_FLAG_DISABLE_DNS_CACHE_LIBINFO", {});
// Force the event loop to use epoll_pwait(2) instead of epoll_pwait2(2).
Expand Down
Loading
Loading