Created
November 13, 2023 14:48
-
-
Save bmeck/b561b34bd8cba538a1eb16168b257684 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference types="node" /> | |
// See NOTES below | |
import flatstr from 'flatstr' | |
import v8 from 'v8' | |
// @ts-check | |
class ArrayOfObjects { | |
store = [] | |
constructor(count) { | |
for (let i = 0; i < count; i++) { | |
this.store.push({ [i]: true }) | |
} | |
} | |
} | |
const n = +process.argv[2] | |
function alloc(name) { | |
return { | |
// NOTES: | |
// this should always be reaped in both | |
// - FIXME? it is held by JSON.stringify even though it need not be currently | |
// - gcstringify it is reaped but that has different semantics | |
// | |
// - if not an accessor the string table is blown out for userland during snapshot | |
// - this happens even when clearing `chunk` below and using `flatstr` oddly | |
// - (Stack roots) shows it is being held even when removing `root` ref in gcstringify | |
// - unclear how this is being held | |
get a() { | |
return new ArrayOfObjects(n || 1e5) | |
}, | |
c: { | |
toJSON() { | |
if (name) v8.writeHeapSnapshot(name+'.heapsnapshot') | |
}, | |
}, | |
} | |
} | |
/** | |
* JSON.stringify but with the following change: | |
* - use iterators and eagerly grab entries() so that previous props can GC | |
*/ | |
function gcstringify(root) { | |
let ret = "" | |
let chunk = '' | |
let stack = [ | |
{ | |
first: true, | |
pendingEntries: [["", root]], | |
tail: "", | |
}, | |
] | |
root = null | |
while (stack.length) { | |
const currentNode = stack.slice(-1)[0] | |
if (currentNode.pendingEntries.length === 0) { | |
if (chunk.length >= 16 * 1024) { | |
ret += flatstr(chunk) | |
chunk = '' | |
} | |
chunk += currentNode.tail | |
stack.pop() | |
continue | |
} | |
const pendingEntry = currentNode.pendingEntries.shift() | |
let value = pendingEntry[1] | |
if ( | |
(typeof value === "object" || typeof value === "bigint") && | |
typeof value.toJSON === "function" | |
) { | |
value = value.toJSON(pendingEntry[0]) | |
} | |
let valueStr = null | |
if (typeof value === "number") { | |
valueStr = JSON.stringify(value) | |
} else if (typeof value === "string") { | |
valueStr = JSON.stringify(value) | |
} else if (typeof value === "boolean") { | |
valueStr = value ? "true" : "false" | |
} else if (value === null) { | |
valueStr = "null" | |
} else if (typeof value === "object") { | |
if (Array.isArray(value)) { | |
valueStr = "[" | |
stack.push({ | |
first: true, | |
pendingEntries: Object.entries(value), | |
tail: "]", | |
}) | |
} else { | |
valueStr = "{" | |
stack.push({ | |
first: true, | |
pendingEntries: Object.entries(value), | |
tail: "}", | |
}) | |
} | |
} | |
if (valueStr !== null) { | |
if (currentNode.first) { | |
currentNode.first = false | |
} else { | |
chunk += "," | |
} | |
if (currentNode.tail === "}") { | |
const key = pendingEntry[0] | |
chunk += `${JSON.stringify(key)}:` | |
} | |
chunk += valueStr | |
if (chunk.length >= 16 * 1024) { | |
ret += flatstr(chunk) | |
chunk = '' | |
} | |
} | |
} | |
return flatstr(ret + chunk) | |
} | |
if (JSON.stringify(alloc()) !== gcstringify(alloc())) { | |
throw new Error("mismatched serialization") | |
} | |
// fill up VM JIT state, MUST BE DONE AFTER BOTH gcstringify and JSON.stringify first run due to string interning | |
JSON.stringify(alloc("warmup")) | |
gcstringify(alloc("gcstringify")) | |
JSON.stringify(alloc("JSON.stringify")) | |
JSON.stringify(alloc("after")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment