[bench] hashing: coerce deno pointers to use numbers - #657
littledivy wants to merge 2 commits into
Conversation
|
I'm a little confused about this. Wouldn't most users just use the built-in |
|
pointer is just a bigint. It's totally practical for performance-hungry code to cast to number and lose some bits, this is Bun's default. |
|
Happy to update to Deno 1.23.4. Not going to do this coercion, as it feels like gaming. Consider making Deno's implementation faster, so that users of Deno benefit. |
|
I just wanted to make the benchmark look fair because it does not take into account how pointers are represented in both runtimes. The code right now is not identical. It's sad that you think this is gaming. |
|
this moves pointer out of benchmark which in some way counts as unrealistic scenario both deno and bun get pointer inside benchmark to match what users would actually do another way to represent this is function hash(buffer) {
....ffi logic....
} |
Brings in, from oven-sh/WebKit main: - #657: a suspended generator or async function keeps its scopes' SymbolTables when its code is generated again - #658: MicrotaskCallCache builds its entries over zeroed storage - #583: RunLoopBun has no weak fallback definition of Bun__thisThreadHasVM (Bun defines it) - #627: JSModuleLoader::clearAll() pins prelinked edges in one pass, which is what graph.dispose() calls; it was quadratic in the size of a prelinked graph
WEBKIT_VERSION 65513e295c73 -> c775a5dc527da387fd84cc444096af38f6810b44 (oven-sh/WebKit main: #628, #630, #635, #657 and two crash-instruction fixes, #316 and #485). No Bun source change is needed to build against it. What the new JavaScriptCore does differently (oven-sh/WebKit#628): a module's function declarations are instantiated when their binding is first read and stay in the embedded bytecode until then; interpreter and Baseline call sites get their link record on their second execution, in either tier, and the collector is told about those records; an unlinked code block's value and array profiles are allocated when it first reaches the Baseline JIT, and value profile predictions only for code that has warmed up; module and program code is released once it has run; code decoded from an embedded bytecode cache can be dropped and decoded again (VM::shrinkFootprintNow with flags; nothing calls it yet), not under a suspended generator or async function activation; the thunks of the call slow paths clear the stack their C++ function's frame is going to occupy, so that what the last callee left there is not kept alive by a later conservative scan; Heap::totalBytesAllocated(), sizeAfterLastCollection() and allocationBudgetThisCycle() for embedders. #630: for-of and array destructuring over arrays without an Array Iterator object, one RegExpObject per /x/.test(s) literal site. #635: Heap::evacuateSparseAuxiliaryBlocks, a prototype nothing calls. #657: a suspended generator keeps its scopes' SymbolTables when its code is generated again. The bytecode cache format revision goes from 5 to 9 (#630 took 6-8, #628 9). test/bundler/bundler_bytecode_portable.test.ts: the inline snapshot of the encoder's output is regenerated with this build. What moved, checked entry by entry: the size and sha256 of every serialized cache (the revision is part of every header, and seven commits in the range change runtime/CachedTypes.cpp: when an unlinked code block's profiles are allocated, its out-of-line jump targets moving into its rare data, how the two-character atom table is indexed, module function declarations that stay in the payload and are instantiated on first read, code that can be decoded again, and #630's bytecode for for-of); the sha256 of the printed JavaScript next to each of them and the string corpus are unchanged. Bundles are 0.1-0.2% smaller (22,009,472 -> 21,977,536 bytes for libraries.js), vm.Script and vm.SourceTextModule caches 0-1.6% larger (typescript.js 11,684,048 -> 11,769,312; acorn.mjs 257,168 -> 261,216, its function declarations are in the payload now). The cases of that file that decode every corpus from its cache and compare the program's output pass, as does the one that encodes in a second process and compares the bytes. test/cli/inspect/compile-bytecode-tooling.test.ts: a module's function declaration now becomes a function object when its binding is first read, so the heap snapshot case takes one snapshot before anything has read hotWork (it is not in it) and one after (it is). [skip size check]: bun-windows-x64 grows by 576 KB, over the 0.5 MB gate (every other target by 64-144 KB). That is the new engine code of the WebKit range plus, on Windows x64 with LTO only, WebKit#316 (BCRASH becomes __builtin_trap on clang-cl), which is about a third of the growth of that target's prebuilt; there is no Bun code in this change to make smaller.
WEBKIT_VERSION 65513e295c73 -> c775a5dc527da387fd84cc444096af38f6810b44 (oven-sh/WebKit main: #628, #630, #635, #657 and two crash-instruction fixes, #316 and #485). No Bun source change is needed to build against it; one workaround in BunDebugger.cpp that the new JavaScriptCore makes redundant goes. What the new JavaScriptCore does differently (oven-sh/WebKit#628): a module's function declarations are instantiated when their binding is first read and stay in the embedded bytecode until then; interpreter and Baseline call sites get their link record on their second execution, in either tier, and the collector is told about those records; an unlinked code block's value and array profiles are allocated when it first reaches the Baseline JIT, and value profile predictions only for code that has warmed up; module and program code is released once it has run; code decoded from an embedded bytecode cache can be dropped and decoded again (VM::shrinkFootprintNow with flags; nothing calls it yet), not under a suspended generator or async function activation; the thunks of the call slow paths clear the stack their C++ function's frame is going to occupy, so that what the last callee left there is not kept alive by a later conservative scan; Heap::totalBytesAllocated(), sizeAfterLastCollection() and allocationBudgetThisCycle() for embedders. #630: for-of and array destructuring over arrays without an Array Iterator object, one RegExpObject per /x/.test(s) literal site. #635: Heap::evacuateSparseAuxiliaryBlocks, a prototype nothing calls. #657: a suspended generator keeps its scopes' SymbolTables when its code is generated again. The bytecode cache format revision goes from 5 to 9 (#630 took 6-8, #628 9). test/bundler/bundler_bytecode_portable.test.ts: the inline snapshot of the encoder's output is regenerated with this build. What moved, checked entry by entry: the size and sha256 of every serialized cache (the revision is part of every header, and seven commits in the range change runtime/CachedTypes.cpp: when an unlinked code block's profiles are allocated, its out-of-line jump targets moving into its rare data, how the two-character atom table is indexed, module function declarations that stay in the payload and are instantiated on first read, code that can be decoded again, and #630's bytecode for for-of); the sha256 of the printed JavaScript next to each of them and the string corpus are unchanged. Bundles are 0.1-0.2% smaller (22,009,472 -> 21,977,536 bytes for libraries.js), vm.Script and vm.SourceTextModule caches 0-1.6% larger (typescript.js 11,684,048 -> 11,769,312; acorn.mjs 257,168 -> 261,216, its function declarations are in the payload now). The cases of that file that decode every corpus from its cache and compare the program's output pass, as does the one that encodes in a second process and compares the bytes. test/cli/inspect/compile-bytecode-tooling.test.ts: a module's function declaration now becomes a function object when its binding is first read, so the heap snapshot case takes one snapshot before anything has read hotWork (it is not in it) and one after (it is). src/jsc/bindings/BunDebugger.cpp: protectModuleExecutablesFromClearCode() took every module executable out of the VM's clearable-code set before the debugger's recompileAllJSFunctions, because ModuleProgramExecutable::clearCode used to drop the module environment's symbol table and regenerating the code in debugger mode no longer matched the live environment. At this WebKit a module executable keeps one environment symbol table and its function declarations for its whole life and its code-generation mode is pinned, so that cannot happen, and module code that has run is released by the engine itself anyway. Removed; test/cli/inspect (48 tests), test/js/node/inspector/inspector.test.ts (24, including the script that turns breakpoints on between two top-level awaits) and test/regression/issue/21654 pass without it. test/cli/inspect/compile-bytecode-tooling.test.ts, new case: Bun.shrink() (like a debugger attaching, or a Worker going away) now drops code that was decoded from a bytecode cache and decodes it again when it next runs; before, such code was never touched. Three rounds of Bun.shrink(), a tick and Bun.gc(true), then a function that had run, one that had not, a class method, a generator and an async function suspended across the shrinks with their captured locals: exact values, and the same output from source, from the --compile --bytecode executable and from NODE_COMPILE_CACHE on its second run. [skip size check]: bun-windows-x64 grows by 576 KB, over the 0.5 MB gate (every other target by 64-144 KB). That is the new engine code of the WebKit range plus, on Windows x64 with LTO only, WebKit#316 (BCRASH becomes __builtin_trap on clang-cl), which is about a third of the growth of that target's prebuilt; there is no Bun code in this change to make smaller.
WEBKIT_VERSION 65513e295c73 -> c775a5dc527da387fd84cc444096af38f6810b44 (oven-sh/WebKit main: #628, #630, #635, #657 and two crash-instruction fixes, #316 and #485). No Bun source change is needed to build against it; one workaround in BunDebugger.cpp that the new JavaScriptCore makes redundant goes. What the new JavaScriptCore does differently (oven-sh/WebKit#628): a module's function declarations are instantiated when their binding is first read and stay in the embedded bytecode until then; interpreter and Baseline call sites get their link record on their second execution, in either tier, and the collector is told about those records; an unlinked code block's value and array profiles are allocated when it first reaches the Baseline JIT, and value profile predictions only for code that has warmed up; module and program code is released once it has run; code decoded from an embedded bytecode cache can be dropped and decoded again (VM::shrinkFootprintNow with flags; nothing calls it yet), not under a suspended generator or async function activation; the thunks of the call slow paths clear the stack their C++ function's frame is going to occupy, so that what the last callee left there is not kept alive by a later conservative scan; Heap::totalBytesAllocated(), sizeAfterLastCollection() and allocationBudgetThisCycle() for embedders. #630: for-of and array destructuring over arrays without an Array Iterator object, one RegExpObject per /x/.test(s) literal site. #635: Heap::evacuateSparseAuxiliaryBlocks, a prototype nothing calls. #657: a suspended generator keeps its scopes' SymbolTables when its code is generated again. The bytecode cache format revision goes from 5 to 9 (#630 took 6-8, #628 9). test/bundler/bundler_bytecode_portable.test.ts: the inline snapshot of the encoder's output is regenerated with this build. What moved, checked entry by entry: the size and sha256 of every serialized cache (the revision is part of every header, and seven commits in the range change runtime/CachedTypes.cpp: when an unlinked code block's profiles are allocated, its out-of-line jump targets moving into its rare data, how the two-character atom table is indexed, module function declarations that stay in the payload and are instantiated on first read, code that can be decoded again, and #630's bytecode for for-of); the sha256 of the printed JavaScript next to each of them and the string corpus are unchanged. Bundles are 0.1-0.2% smaller (22,009,472 -> 21,977,536 bytes for libraries.js), vm.Script and vm.SourceTextModule caches 0-1.6% larger (typescript.js 11,684,048 -> 11,769,312; acorn.mjs 257,168 -> 261,216, its function declarations are in the payload now). The cases of that file that decode every corpus from its cache and compare the program's output pass, as does the one that encodes in a second process and compares the bytes. test/cli/inspect/compile-bytecode-tooling.test.ts: a module's function declaration now becomes a function object when its binding is first read, so the heap snapshot case takes one snapshot before anything has read hotWork (it is not in it) and one after (it is). src/jsc/bindings/BunDebugger.cpp: protectModuleExecutablesFromClearCode() took every module executable out of the VM's clearable-code set before the debugger's recompileAllJSFunctions, because ModuleProgramExecutable::clearCode used to drop the module environment's symbol table and regenerating the code in debugger mode no longer matched the live environment. At this WebKit a module executable keeps one environment symbol table and its function declarations for its whole life and its code-generation mode is pinned, so that cannot happen, and module code that has run is released by the engine itself anyway. Removed; test/cli/inspect (48 tests), test/js/node/inspector/inspector.test.ts (24, including the script that turns breakpoints on between two top-level awaits) and test/regression/issue/21654 pass without it. test/cli/inspect/compile-bytecode-tooling.test.ts, new case: Bun.shrink() (like a debugger attaching, or a Worker going away) now drops code that was decoded from a bytecode cache and decodes it again when it next runs; before, such code was never touched. Three rounds of Bun.shrink(), a tick and Bun.gc(true), then a function that had run, one that had not, a class method, a generator and an async function suspended across the shrinks with their captured locals: exact values, and the same output from source, from the --compile --bytecode executable and from NODE_COMPILE_CACHE on its second run. [skip size check]: bun-windows-x64 grows by 576 KB, over the 0.5 MB gate (every other target by 64-144 KB). That is the new engine code of the WebKit range plus, on Windows x64 with LTO only, WebKit#316 (BCRASH becomes __builtin_trap on clang-cl), which is about a third of the growth of that target's prebuilt; there is no Bun code in this change to make smaller.
#42822) ### What does this PR do? Moves `WEBKIT_VERSION` from `65513e295c73` to `c775a5dc527da387fd84cc444096af38f6810b44` (oven-sh/WebKit main). No Bun source change is needed to build: main builds against it as it is. One workaround the new engine makes redundant is removed (`BunDebugger.cpp`, below); the other two files are tests. What comes with it, on the JavaScriptCore side: - oven-sh/WebKit#628, "less memory per function that never runs, and code that can be dropped and decoded again": - a module's function declarations are instantiated when their binding is first read, and stay in the embedded bytecode until then; - call sites in the interpreter and in Baseline code get their link record (and array profile) on their second execution, in either tier, and the collector is told about those records; - an unlinked code block's value and array profiles are allocated when it first reaches the Baseline JIT, and a metadata table's value-profile predictions are kept out of line and only for code that has warmed up; - module and program code that has run is released right away; a module record that is not going to run its body is done with its executable's code; - code decoded from an embedded bytecode cache (`bun build --compile --bytecode`) can be dropped and decoded again (`VM::shrinkFootprintNow` with flags; nothing in Bun calls it yet), not under a suspended generator or async activation; a released RegExp keeps its atom; - the thunks of the call slow paths clear the stack their C++ function's frame is going to occupy, so that what the last callee left there is not kept alive by a later conservative scan; - `Heap::totalBytesAllocated()`, `sizeAfterLastCollection()` and `allocationBudgetThisCycle()` for embedders; `$vm.codeBlockCensus()`. - Consequence for Bun: `Bun.shrink()`, a debugger attaching (`recompileAllJSFunctions`) and a Worker going away (`VM::deleteAllCode`) now also drop executables that were decoded from a persistent payload (`--compile --bytecode`, `NODE_COMPILE_CACHE`) and decode them again on their next call; before, such code was never touched. There is a test for it now (below). `Bun.shrink()` also returns early, without collecting or returning allocator memory, when a concurrent collection is in progress, and it clears `vm.lastException()`; both are benign. - oven-sh/WebKit#630: `for`-`of` and array destructuring over arrays without an Array Iterator object, and one RegExpObject per `/x/.test(s)` literal site. - oven-sh/WebKit#635: `Heap::evacuateSparseAuxiliaryBlocks` (a prototype; nothing calls it by default). - oven-sh/WebKit#657: a suspended generator or async function keeps its scopes' SymbolTables when its code is generated again. - The bytecode cache format revision goes from 5 to 9 (#630 took 6-8, #628 takes 9), so caches written by an older Bun are rejected and rebuilt, and `--compile --bytecode` executables must be rebuilt with the new Bun (as with every revision bump). The release `autobuild-c775a5dc527da387fd84cc444096af38f6810b44` is published with the same 42 assets, name for name, as the release of the current pin (`autobuild-65513e295c73...`), so every flavour `scripts/build/deps/webkit.ts` downloads today exists for this one: linux glibc, musl and android, macos, windows and freebsd, amd64 and arm64, each as release, `-lto` (not windows-arm64, as today) and `-debug`, plus `-asan` and `-debug-asan` for linux amd64/arm64 and macos-arm64 and `-asan` for windows-amd64. `src/jsc/bindings/BunDebugger.cpp`: `protectModuleExecutablesFromClearCode()` removed every module executable from the VM's clearable-code set before the debugger's `recompileAllJSFunctions`, because `ModuleProgramExecutable::clearCode` used to drop the module environment's symbol table, and code regenerated in debugger mode then no longer matched the live environment. At this WebKit a module executable keeps one environment symbol table and its function declarations for its whole life, its code-generation mode is pinned, and module code that has run is released by the engine itself, so the invariant the comment described no longer exists. The function and its call are deleted. ### What it gives A large bundled CLI application built as a standalone executable with embedded bytecode (`--compile --bytecode`), a scripted 20-request session against a local fake API, release builds (`--lto=off`) of main (782c402) and of this branch, same application commit. RSS in MB (anonymous + file-backed). | | main | this PR | |---|---|---| | parked at its first prompt, 10 s after it appears | 229 (133 + 95) | 207-213 (116 + 90-97) | | ... 30 s | 228-229 (133-134 + 95) | 206-213 (114-116 + 92-96) | | ... 2.5 min (after main's second idle collection and the page-out of the module graph) | 140-145 (108 + 32-36) | 136-140 (95-99 + 41) | | ... 10 min | 142-145 (108 + 33-37) | 135-141 (93-98 + 42-43) | | **peak of the 20-request session** (10 runs each, interleaved; the larger of the kernel's `VmHWM` and the highest `VmRSS` seen) | **median 474, range 452-490** | **median 453, range 434-472** | | what the highest sample of those runs is made of (medians) | 368 anonymous + 103 file | 342 anonymous + 105 file | | 30 s after the session | 385-391 (282-288 + 102-103) | 353-359 (259-260 + 94-98) | | ... 2.5 min | 215-216 (178 + 37-38) | 210-213 (167-171 + 42-43) | | ... 10 min | 225-231 (179-183 + 45-47) | 217-224 (168-171 + 49-52) | | `--help`, user-mode instructions (7 runs each, interleaved) | 548.5 M (543.0-548.8) | 507.9 M (506.6-528.8): -7.4% | Two runs per row unless said otherwise; the idle timer is main's on both sides (its defaults: collections after 10 s, 75 s and 140 s without heap growth). What it is: about 17 MB less anonymous memory from the moment the prompt is up (functions that are declared and never called no longer exist as objects, call sites that have run once or never carry no link record, code that never reached the Baseline JIT has no profiles), about 10 MB of that left once the idle collections have run on both sides, and 5-9 MB more file-backed memory at rest (the declarations that are not instantiated stay in the embedded bytecode, which is part of the executable's mapping). The peak is lower too, by about the same amount as the fresh prompt and a little more: 22 MB at the median, all of it anonymous; the spread between runs is 40 MB on both sides, and this PR is the lower one in 9 of the 10 interleaved pairs. So the lazily allocated metadata lowers the peak as well as the idle footprint, by roughly what it saves at the prompt; nothing here changes what a request allocates while it runs. ### Binary size Against main's canary (BuildKite's binary-size annotation on this PR's first build): | target | main | this PR | delta | |---|---|---|---| | bun-darwin-aarch64 | 59.95 MB | 60.07 MB | +129.2 KB | | bun-darwin-x64 | 65.83 MB | 65.97 MB | +144.3 KB | | bun-linux-aarch64 | 76.43 MB | 76.55 MB | +128.0 KB | | bun-linux-x64 | 76.38 MB | 76.51 MB | +128.0 KB | | bun-linux-aarch64-musl | 69.63 MB | 69.70 MB | +64.0 KB | | bun-linux-x64-musl | 70.51 MB | 70.59 MB | +80.0 KB | | bun-linux-aarch64-android | 83.40 MB | 83.47 MB | +64.0 KB | | bun-linux-x64-android | 85.84 MB | 85.95 MB | +112.0 KB | | bun-freebsd-x64 | 87.94 MB | 88.06 MB | +124.0 KB | | bun-freebsd-aarch64 | 91.29 MB | 91.41 MB | +128.0 KB | | **bun-windows-x64** | 82.58 MB | 83.14 MB | **+575.5 KB** | | bun-windows-aarch64 | 72.27 MB | 72.37 MB | +101.5 KB | `bun-windows-x64` is over the 0.5 MB gate, so the commit carries `[skip size check]`. Where that target's growth comes from, by the size of the published WebKit prebuilt (`.tar.gz` bytes, which are not linked bytes, but the shape is clear) at each commit of the range: | oven-sh/WebKit main at | windows-amd64-lto | step | linux-amd64-lto step | |---|---|---|---| | 65513e29 (current pin) | 646,774,059 | | | | a76e6b08 (#316: `BCRASH` is `__builtin_trap` on clang-cl) | 648,250,706 | +1.48 MB | +1.4 KB (windows-arm64: +19 KB) | | 03897bd1 (#630) | 649,000,141 | +0.75 MB | +0.31 MB | | 16a8ab73 (#635, #485) | 649,449,191 | +0.45 MB | +0.15 MB | | c775a5dc (#628) | 651,166,232 | +1.72 MB | +0.63 MB | About a third of the Windows x64 growth is #316, a crash-instruction fix that only shows on Windows x64 with LTO and that any upgrade past it brings; the rest is the new engine code (#628, #630, #635), which costs 64-144 KB on every other target. There is no Bun code in this PR to make smaller. #42383's earlier builds, on a preview of #628 alone, passed the gate. ### How did you verify your code works? Release build (`bun scripts/build.ts --profile=release --lto=off`) with the downloaded prebuilt; the same build of main next to it. - `test/bundler/bundler_bytecode_portable.test.ts`: the pinned encoder output changes on a WebKit upgrade that changes the cache format, and the file says to regenerate it then. Regenerated with this build and checked entry by entry: only the `bytes` and `sha256` of the serialized caches move (every one of them: the revision is in every header, and seven commits in the range change `runtime/CachedTypes.cpp`); the `sha256` of the printed JavaScript next to each and the string corpus are unchanged. Bundles are 0.1-0.2% smaller (`libraries.js` 22,009,472 -> 21,977,536 bytes), `vm.Script` / `vm.SourceTextModule` caches 0-1.6% larger (`typescript.js` 11,684,048 -> 11,769,312; `acorn.mjs` 257,168 -> 261,216: its function declarations are in the payload now). The cases that decode every corpus from its cache and compare the program's output, and the one that encodes in a second process and compares the bytes, pass: 22 of 22. CI runs the same snapshot on every platform, which is the point of the test. - `test/cli/inspect/compile-bytecode-tooling.test.ts`: the heap-snapshot case expected `hotWork`, a module function nobody had read, to be in the snapshot. A module's function declaration is now created when its binding is first read, so that cannot hold; the case now takes one snapshot before anything reads `hotWork` (it is absent) and one after (it is present). - New case in `compile-bytecode-tooling.test.ts`, "code dropped by Bun.shrink() is decoded again and runs the same": three rounds of `Bun.shrink()`, a tick and `Bun.gc(true)`, then a function that had run, one that had not, a class method, a generator suspended across the shrinks (its captured locals are the shape #657 fixes) and a pending async function. It asserts exact values, and that the output is identical from source, from the `--compile --bytecode` executable and from `NODE_COMPILE_CACHE` on its second run (the cache directory is checked to have been written). 5 of 5. - Without the `BunDebugger.cpp` workaround: `test/cli/inspect` 48 pass (one run had the port-collision flake of `websocket > bun --inspect=<port>`, which passed on the next), `test/js/node/inspector/inspector.test.ts` 24 of 24 (it has the script that turns breakpoints on between two top-level awaits, which is the case the workaround was for), `test/regression/issue/21654` 1 of 1; the same counts with it. - `test/cli/run/require-cache.test.ts` 18/18, `test/js/workerd/html-rewriter-leak.test.ts` 13 pass / 1 skip, `test/js/bun/jsc/heapStats-mimalloc.test.ts` 7 pass / 1 skip. - `test/js/bun/resolve` (41 files): 360 pass, 3 fail; `test/js/web/fetch/fetch.test.ts`: 361 pass, 4 fail; `test/js/bun/http/serve.test.ts`: 303 pass, 1 fail. The failures are the same ones, test for test, with the build of main on the same machine (they need a user that is not root: unreadable directories and files, a port below 1024). - No Rust changes, so there is nothing for clippy or the cross-target `cargo check` to look at; the C++ change is a deletion.
also, can you update Deno to 1.23.4? thanks