| Summary: | Object.entries() is 1.5x slower in JSC compared to V8 | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Product: | WebKit | Reporter: | Jarred Sumner <jarred> | ||||||
| Component: | JavaScriptCore | Assignee: | Yusuke Suzuki <ysuzuki> | ||||||
| Status: | RESOLVED FIXED | ||||||||
| Severity: | Normal | CC: | mark.lam, webkit-bug-importer, ysuzuki | ||||||
| Priority: | P2 | Keywords: | InRadar | ||||||
| Version: | WebKit Nightly Build | ||||||||
| Hardware: | Unspecified | ||||||||
| OS: | Unspecified | ||||||||
| Attachments: |
|
||||||||
Created attachment 462784 [details]
rollup.browser.js
Here is a build of Rollup.js that will run in JSC
If you run `jsc -m rollup.browser.js` with a file named "file.js" in the same cwd it should build file.js
If you uncomment the last line it will print it to the terminal, but since we are not benchmarking printing to stdout from this function it is commented out
Note 1. We should apply Object.assign’s optimization 2. We should wire Object.entries with own-keys cache to reuse string cells. Pull request: https://github.com/WebKit/WebKit/pull/5281 Committed 255470@main (2b999503e689): <https://commits.webkit.org/255470@main> Reviewed commits have been landed. Closing PR #5281 and removing active labels. |
Created attachment 462781 [details] Launch_bun_2022-10-03_23.22.34_2576143F.trace.zip I've attached an Instruments trace and sampling profiler output of using Rollup.js to bundle the "@babel/standalone" npm package. End-to-end, it runs about 1.6x slower in JSC (bun) compared to V8 (node). Nearly all the time is spent in JSC. I think it's _mostly_ because of Object.entries() (which is why the issue is titled about Object.entries()). Rollup seems to use Object.entries() in their code a _lot_. In @babel/standalone's case, Object.entries() is called 550,000 times. For larger objects, Object.entries() is around 1.5x slower than in V8 (microbenchmark). It looks like ObjectKeys exists in DFGSpeculateJIT64.cpp but ObjectEntries and ObjectValues do not. Maybe adding it as a JIT operation would help? I don't know if that's the whole story though. It feels like there's a side effects tracking thing that would enable skipping some of the PropertyTable::find lookups, maybe? ``` ❯ bun /Users/jarred/Code/bun/bench/snippets/object-entries.mjs cpu: Apple M1 Max runtime: bun 0.1.14 (arm64-darwin) benchmark time (avg) (min … max) p75 p99 p995 --------------------------------------------------------------- ----------------------------- Object.entries(26 keys) 1.27 µs/iter (1.21 µs … 1.99 µs) 1.28 µs 1.99 µs 1.99 µs Object.keys(26 keys) 515.77 ns/iter (477.57 ns … 628.68 ns) 526.63 ns 617.05 ns 628.68 ns Object.entries(2 keys) 132.46 ns/iter (125.25 ns … 205.22 ns) 127.54 ns 194.48 ns 196.87 ns Object.keys(2 keys) 4.68 ns/iter (4.12 ns … 219.59 ns) 4.42 ns 7.48 ns 10.22 ns ``` ``` ❯ node /Users/jarred/Code/bun/bench/snippets/object-entries.mjs cpu: Apple M1 Max runtime: node v18.9.1 (arm64-darwin) benchmark time (avg) (min … max) p75 p99 p995 --------------------------------------------------------------- ----------------------------- Object.entries(26 keys) 830.02 ns/iter (803.19 ns … 1.07 µs) 821.85 ns 1.07 µs 1.07 µs Object.keys(26 keys) 327.72 ns/iter (319.58 ns … 421.81 ns) 329.03 ns 400.85 ns 421.81 ns Object.entries(2 keys) 98.28 ns/iter (96.05 ns … 452.63 ns) 97.19 ns 105.5 ns 108.87 ns Object.keys(2 keys) 13.36 ns/iter (10.75 ns … 76.29 ns) 15.3 ns 17.92 ns 21.13 ns ``` - Benchmark: ``` import { bench, run } from "../../node_modules/mitata/src/cli.mjs"; const obj = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, u: 21, v: 22, w: 23, x: 24, y: 25, z: 26, }; bench("Object.entries(26 keys)", () => { var k; for (let [key, value] of Object.entries(obj)) { value = key; } return k; }); bench("Object.keys(26 keys)", () => { var k; for (let [key, value] of Object.keys(obj)) { value = key; } return k; }); bench("Object.entries(2 keys)", () => { var k; for (let [key, value] of Object.entries({ a: 1, b: 2 })) { value = key; } return k; }); bench("Object.keys(2 keys)", () => { var k; for (let item of Object.keys({ a: 1, b: 2 })) { } return k; }); await run(); ``` Sampling profiler output: Sampling rate: 100.000000 microseconds. Total samples: 19077 Top functions as <numSamples 'functionName#hash:sourceID'> 4028 'include#<nil>:5' 1552 'parseNode#<nil>:5' 1299 '#<nil>:5' 1089 'entries#<nil>:4294967295' 979 'includeCallArguments#<nil>:5' 809 'create#<nil>:4294967295' 656 'hasEffects#<nil>:5' 606 'arrayIteratorNextHelper#<nil>:7' 568 'NodeBase#<nil>:5' 547 '#<nil>:4294967295' 506 'bind#<nil>:5' 495 'render#<nil>:5' Sampling rate: 100.000000 microseconds. Total samples: 19077 Tier breakdown: ----------------------------------- LLInt: 184 (0.964512%) Baseline: 935 (4.901190%) DFG: 3991 (20.920480%) FTL: 10469 (54.877601%) js builtin: 862 (4.518530%) Wasm: 0 (0.000000%) Host: 559 (2.930230%) RegExp: 115 (0.602820%) C/C++: 0 (0.000000%) Unknown Frame: 4 (0.020968%) Unknown Executable: 2820 (14.782198%) Hottest bytecodes as <numSamples 'functionName#hash:JITType:bytecodeIndex'> 1089 'entries#<nil>:None:<nil>' 1001 'include#<nil>:FTL:bc#33' 809 'create#<nil>:None:<nil>' 575 'parseNode#<nil>:FTL:bc#218' 547 '#<nil>:None:<nil>' 443 'parseNode#<nil>:FTL:bc#479' 411 'Set#<nil>:None:<nil>' 404 'include#<nil>:FTL:bc#107' 363 'include#<nil>:FTL:bc#296' 337 'include#<nil>:FTL:bc#93' 280 'arrayIteratorNextHelper#<nil>:FTL:bc#122' 272 'include#<nil>:FTL:bc#54' 251 'arrayIteratorNextHelper#<nil>:FTL:bc#58' 221 'includeCallArguments#<nil>:FTL:bc#59' 206 'includeProperties#<nil>:FTL:bc#59' 127 'Map#<nil>:None:<nil>' 117 'getEntities#<nil>:FTL:bc#180' 111 'include#<nil>:FTL:bc#0' 107 'parseModule#<nil>:None:<nil>' 106 'values#<nil>:None:<nil>' 106 'parseNode#<nil>:FTL:bc#760' 95 'includeCallArguments#<nil>:DFG:bc#201' 90 'include#<nil>:FTL:bc#138' 87 'getEntities#<nil>:FTL:bc#76' 84 'includeCallArguments#<nil>:FTL:bc#0' 81 'bind#<nil>:FTL:bc#262' 81 'delete#<nil>:None:<nil>' 81 'include#<nil>:FTL:bc#196' 77 '#<nil>:FTL:bc#0' 75 'createScope#<nil>:FTL:bc#11' 73 'include#<nil>:FTL:bc#27' 73 'include#<nil>:FTL:<nil>' 72 '/^(?:break|case|catch|continue|debugger|default|do|else|finally|for|function|if|return|switch|throw|try|var|while|with|null|true|false|instanceof|typeof|void|delete|new|in|this|const|class|extends|export|import|super)$/#<nil>:RegExp:<nil>' 72 'bind#<nil>:FTL:bc#65' 71 'includeCallArguments#<nil>:FTL:<nil>' 71 'includeProperties#<nil>:FTL:bc#19' 70 'include#<nil>:DFG:bc#54' 66 'includeProperties#<nil>:FTL:bc#70' 65 'includeUnknownTest#<nil>:DFG:bc#22' 65 'include#<nil>:FTL:bc#229'