mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Test for-in key presence in the overflow map instead of a fresh key list (#1859)
This commit is contained in:
@@ -11,6 +11,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea
|
||||
| `channel-messages` | cpu | 200,000 MessageChannel messages in one thread, half numbers and half small objects, all posted before the port delivers any |
|
||||
| `csv-numbers` | cpu | `split`, `Number`, `parseInt`, `toFixed` |
|
||||
| `cyclic-graphs` | cpu | object graphs with back references built and dropped: trees with parent and sibling links, rings of records sharing a long-lived registry, closures capturing their owner |
|
||||
| `dictionary-walks` | cpu | `Record<string, number>` dictionaries walked with for-in and `Object.keys`/`values`/`entries` while entries are added, updated and deleted |
|
||||
| `functional-pipeline` | cpu | `filter`/`map`/`reduce`/`flatMap` chains, closures, small records |
|
||||
| `intern-table`, `intern-table-map`, `intern-table-1`, `intern-table-map-1` | cpu | worker threads interning the same type-like objects under string keys and records under number keys, then hits and misses: one shared `InternTable` per kind of key, or a module-level `Map` per thread (the `-map` entries), with 8 threads or 1 (the `-1` entries); under Node.js every thread has its own tables |
|
||||
| `json-records` | cpu | `JSON.stringify`, typed `JSON.parse`, Map aggregation |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"entry": "workloads/dictionary-walks.ts",
|
||||
"args": [
|
||||
"1"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// String-keyed dictionaries (`Record<string, number>`) walked with for-in,
|
||||
// Object.keys, Object.values and Object.entries while entries are added,
|
||||
// updated and deleted: per-category totals, a pruning pass and a merge.
|
||||
let seed = 777;
|
||||
function random(): number {
|
||||
seed = (Math.imul(seed, 1103515245) + 12345) >>> 0;
|
||||
return seed / 4294967296;
|
||||
}
|
||||
|
||||
const scale = Number(process.argv[2] ?? "1");
|
||||
const categories = 4000;
|
||||
const counts: Record<string, number> = {};
|
||||
for (let i = 0; i < Math.floor(200000 * scale); i++) {
|
||||
const key = "c" + Math.floor(random() * categories);
|
||||
counts[key] = (counts[key] ?? 0) + 1;
|
||||
}
|
||||
|
||||
let walked = 0;
|
||||
let sum = 0;
|
||||
for (let pass = 0; pass < 40; pass++) {
|
||||
for (const key in counts) {
|
||||
walked++;
|
||||
sum += counts[key]!;
|
||||
}
|
||||
}
|
||||
|
||||
// Prune: delete the small categories during the walk, including keys the
|
||||
// walk has not reached yet.
|
||||
let pruned = 0;
|
||||
for (const key in counts) {
|
||||
const n = counts[key]!;
|
||||
if (n < 45) {
|
||||
delete counts[key];
|
||||
pruned++;
|
||||
}
|
||||
const ahead = "c" + ((Number(key.slice(1)) + 1) % categories);
|
||||
if (n > 60 && ahead in counts && counts[ahead]! < 45) {
|
||||
delete counts[ahead];
|
||||
pruned++;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge into a second dictionary and read it back every way.
|
||||
const merged: Record<string, number> = {};
|
||||
for (const key of Object.keys(counts)) merged[key.toUpperCase()] = counts[key]! * 2;
|
||||
let values = 0;
|
||||
for (const v of Object.values(merged)) values += v;
|
||||
let entries = 0;
|
||||
for (const [key, v] of Object.entries(merged)) entries += key.length + v;
|
||||
|
||||
console.log(
|
||||
`walked ${walked}, sum ${sum}, pruned ${pruned}, kept ${Object.keys(counts).length}, values ${values}, entries ${entries}`,
|
||||
);
|
||||
@@ -11484,11 +11484,13 @@ function lowerForIn(lowerer: Lowerer, stmt: ts.ForInStatement): IrStmt {
|
||||
keysT,
|
||||
loc,
|
||||
);
|
||||
// The overflow map's hashed key set answers presence directly; a
|
||||
// fresh key list per visit would make the walk quadratic.
|
||||
const guard = (kRef: IrExpr): IrExpr => ({
|
||||
kind: "arrIntrinsic",
|
||||
method: "includes",
|
||||
receiver: { kind: "recordOvfKeys", obj: recvRef(), shapeId, type: keysT, loc },
|
||||
args: [kRef],
|
||||
kind: "recordOvfHas",
|
||||
obj: recvRef(),
|
||||
shapeId,
|
||||
key: kRef,
|
||||
type: BOOL,
|
||||
loc,
|
||||
});
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7011-for-in-dictionary-deletes.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// for-in over a string-keyed dictionary visits the keys present when the
|
||||
// walk starts, in insertion order, and skips a key deleted before its
|
||||
// turn; keys added during the walk are not visited. A key deleted and
|
||||
// added again before its turn is visited.
|
||||
const d: Record<string, number> = {};
|
||||
for (let i = 0; i < 8; i++) d["k" + i] = i;
|
||||
const seen: string[] = [];
|
||||
for (const key in d) {
|
||||
seen.push(key);
|
||||
if (key === "k1") {
|
||||
delete d["k3"];
|
||||
delete d["k0"];
|
||||
d["k8"] = 8;
|
||||
}
|
||||
if (key === "k2") {
|
||||
delete d["k5"];
|
||||
d["k5"] = 50;
|
||||
}
|
||||
if (key === "k4") delete d["k4"];
|
||||
}
|
||||
console.log(seen.join(","));
|
||||
console.log(Object.keys(d).join(","), JSON.stringify(d));
|
||||
|
||||
// A numeric-looking key is walked in integer order before string keys.
|
||||
const n: Record<string, string> = {};
|
||||
n["b"] = "x";
|
||||
n["2"] = "y";
|
||||
n["10"] = "z";
|
||||
n["a"] = "w";
|
||||
const order: string[] = [];
|
||||
for (const key in n) {
|
||||
order.push(key);
|
||||
if (key === "2") delete n["a"];
|
||||
}
|
||||
console.log(order.join(","));
|
||||
|
||||
// A larger dictionary walked many times.
|
||||
const big: Record<string, number> = {};
|
||||
for (let i = 0; i < 3000; i++) big["e" + i] = i;
|
||||
let total = 0;
|
||||
for (let pass = 0; pass < 20; pass++) for (const key in big) total += big[key]!;
|
||||
console.log(total);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1527, "san": 2135 }
|
||||
Reference in New Issue
Block a user