@fraczak/k 7.1.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/DOCS/BIG_PICTURE.md +54 -7
- package/DOCS/CODECS.md +47 -2
- package/DOCS/CODEC_ARCHITECTURE.md +14 -5
- package/DOCS/DICTIONARY.md +13 -4
- package/DOCS/FILE_FORMATS.md +3 -2
- package/DOCS/JSON_APPROACHES_COMPARISON.md +306 -0
- package/DOCS/NAMED_PATTERNS.md +317 -0
- package/DOCS/PATTERNS.md +59 -11
- package/DOCS/REPL.md +24 -5
- package/DOCS/WEB_REPL_REQUIREMENTS.md +4 -2
- package/Examples/arithmetics.k +22 -17
- package/Examples/bnat.k +21 -11
- package/Examples/buda.k +37 -20
- package/Examples/byte.k +3 -1
- package/Examples/code.k +38 -39
- package/Examples/djson-btrie.k +414 -0
- package/Examples/djson-ptrie.k +663 -0
- package/Examples/djson.k +323 -0
- package/Examples/esc.k +1 -1
- package/Examples/karatsuba-mult.k +16 -13
- package/Examples/list.k +8 -4
- package/Examples/nat.k +17 -11
- package/Examples/poly.k +11 -3
- package/Examples/vhdl_to_k.k +43 -31
- package/README.md +42 -15
- package/Value.mjs +28 -3
- package/backend-api.mjs +5 -1
- package/browser/repl-browser.mjs +6 -0
- package/browser/tree-view.mjs +82 -24
- package/browser/vfs-data.mjs +21 -12
- package/codecs/README.md +3 -0
- package/codecs/djson-btrie.mjs +560 -0
- package/codecs/djson-ptrie.mjs +587 -0
- package/codecs/djson.mjs +270 -0
- package/codecs/int.mjs +2 -1
- package/codecs/json.mjs +7 -2
- package/codecs/k-parse.mjs +7 -1
- package/codecs/k-print.mjs +4 -0
- package/codecs/runtime/codec-sdk.mjs +16 -2
- package/codecs/runtime/pattern-json.mjs +80 -1
- package/codecs/runtime/prefix-codec.mjs +629 -67
- package/codecs/runtime/show-value.mjs +93 -51
- package/codecs/runtime/unicode-string.mjs +29 -6
- package/codecs/show.mjs +4 -0
- package/codecs/utf16.mjs +2 -1
- package/core.k +79 -115
- package/index.mjs +3 -0
- package/named-patterns.mjs +267 -0
- package/object.mjs +31 -3
- package/package.json +5 -1
- package/parser.jison +68 -5
- package/parser.mjs +111 -60
- package/pretty.mjs +5 -0
- package/repl.mjs +245 -28
- package/scripts/build-repl-html.mjs +76 -5
- package/scripts/perf-djson.mjs +1142 -0
- package/scripts/run-tests.mjs +5 -0
- package/symbol-table.mjs +20 -2
- package/tests/bench-djson-tries.mjs +340 -0
- package/tests/test-codecs.mjs +211 -0
- package/tests/test-named-patterns.mjs +189 -0
- package/tests/test-repl-html.mjs +52 -4
- package/tests/test-repl.mjs +223 -0
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { performance } from "node:perf_hooks";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
codes,
|
|
9
|
+
decodeWire,
|
|
10
|
+
encodeToWire,
|
|
11
|
+
executeKVM
|
|
12
|
+
} from "../backend-api.mjs";
|
|
13
|
+
import { createState, evaluateInput } from "../repl.mjs";
|
|
14
|
+
import * as jsonCodec from "../codecs/json.mjs";
|
|
15
|
+
import * as djsonCodec from "../codecs/djson.mjs";
|
|
16
|
+
import btrieCodec, {
|
|
17
|
+
bitPathToIndex,
|
|
18
|
+
indexToBitPath
|
|
19
|
+
} from "../codecs/djson-btrie.mjs";
|
|
20
|
+
import ptrieCodec from "../codecs/djson-ptrie.mjs";
|
|
21
|
+
import {
|
|
22
|
+
compileWasmArtifactFromObject,
|
|
23
|
+
instantiateWasmArtifact,
|
|
24
|
+
writeValueToArena
|
|
25
|
+
} from "../backends/wasm/src/wasm.mjs";
|
|
26
|
+
import {
|
|
27
|
+
compileCaseExecutable,
|
|
28
|
+
prepareRelation,
|
|
29
|
+
runExecutable,
|
|
30
|
+
runExecutableMainBench,
|
|
31
|
+
wireInput
|
|
32
|
+
} from "../backends/llvm/tests/perf-support.mjs";
|
|
33
|
+
|
|
34
|
+
function printHelp() {
|
|
35
|
+
console.log(`Usage: node scripts/perf-djson.mjs [options]
|
|
36
|
+
|
|
37
|
+
Dynamic JSON Codecs & Schemas Benchmark Suite.
|
|
38
|
+
Compares the three universal dynamic JSON implementations:
|
|
39
|
+
- djson (sorted cons-list objects & cons-list arrays)
|
|
40
|
+
- djson-btrie (uncompressed binary bit-trie objects & arrays)
|
|
41
|
+
- djson-ptrie (Patricia/Radix trie objects & arrays, trie-array style)
|
|
42
|
+
and contrasts codec wire sizes against the schema-specializing json codec.
|
|
43
|
+
|
|
44
|
+
Part 1 benchmarks codec operations (parse, encodeToWire, decodeWire,
|
|
45
|
+
print, and wire payload sizes) across 5 value categories:
|
|
46
|
+
1. Shared-prefix objects
|
|
47
|
+
2. Varied-key objects
|
|
48
|
+
3. Short-key objects
|
|
49
|
+
4. Homogeneous number arrays
|
|
50
|
+
5. Mixed nested documents (arrays of objects)
|
|
51
|
+
|
|
52
|
+
Part 2 benchmarks K schema relations (dot, has_key?, nth, obj_size,
|
|
53
|
+
list_size, remove, put) across kVM, WebAssembly, PKIR (Wasm), and
|
|
54
|
+
LLVM -O3 native binaries.
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--sizes <csv> Comma-separated sizes N (default: 10,100,500)
|
|
58
|
+
--calls <n> Calls per relation benchmark (default: 1000)
|
|
59
|
+
--codec-iters <n> Iterations for codec timing (default: 100)
|
|
60
|
+
--backends <csv> Backends to run: kvm,wasm,pkir,llvm (default: all)
|
|
61
|
+
--codecs-only Run only Part 1 (Codec & Wire Format benchmark)
|
|
62
|
+
--schemas-only Run only Part 2 (Schema Relation Execution benchmark)
|
|
63
|
+
--json Print machine-readable JSON report at the end
|
|
64
|
+
-h, --help Show this help message and exit
|
|
65
|
+
|
|
66
|
+
Environment Variables:
|
|
67
|
+
SIZES Comma-separated sizes (e.g. SIZES=10,100,500)
|
|
68
|
+
CALLS Number of calls per relation case (default: 1000)
|
|
69
|
+
CODEC_ITERS Iterations for codec microbenchmarks (default: 100)
|
|
70
|
+
BACKENDS Target backends (e.g. BACKENDS=wasm,llvm)`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
74
|
+
printHelp();
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getArgValue(flag) {
|
|
79
|
+
const idx = process.argv.indexOf(flag);
|
|
80
|
+
if (idx !== -1 && idx + 1 < process.argv.length) {
|
|
81
|
+
return process.argv[idx + 1];
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const sizesRaw =
|
|
87
|
+
getArgValue("--sizes") || process.env.SIZES || "10,100,500";
|
|
88
|
+
const SIZES = sizesRaw
|
|
89
|
+
.split(",")
|
|
90
|
+
.map((s) => Number.parseInt(s.trim(), 10))
|
|
91
|
+
.filter((n) => Number.isFinite(n) && n > 0);
|
|
92
|
+
|
|
93
|
+
const CALLS = Number.parseInt(
|
|
94
|
+
getArgValue("--calls") || process.env.CALLS || "1000",
|
|
95
|
+
10
|
|
96
|
+
);
|
|
97
|
+
const CODEC_ITERS = Number.parseInt(
|
|
98
|
+
getArgValue("--codec-iters") || process.env.CODEC_ITERS || "100",
|
|
99
|
+
10
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const backendsRaw = (
|
|
103
|
+
getArgValue("--backends") ||
|
|
104
|
+
process.env.BACKENDS ||
|
|
105
|
+
"kvm,wasm,pkir,llvm"
|
|
106
|
+
).toLowerCase();
|
|
107
|
+
const BACKENDS = new Set(
|
|
108
|
+
backendsRaw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const runCodecsPart = !process.argv.includes("--schemas-only");
|
|
112
|
+
const runSchemasPart = !process.argv.includes("--codecs-only");
|
|
113
|
+
const emitJson = process.argv.includes("--json");
|
|
114
|
+
|
|
115
|
+
// -------------------------------------------------------------------
|
|
116
|
+
// Dataset Generators
|
|
117
|
+
// -------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
const WORDS = [
|
|
120
|
+
"account", "balance", "category", "database", "endpoint",
|
|
121
|
+
"filename", "geometry", "hostname", "identity", "journal",
|
|
122
|
+
"keyboard", "language", "metadata", "network", "operator",
|
|
123
|
+
"password", "quantity", "resource", "security", "template",
|
|
124
|
+
"username", "validate", "workflow", "x509cert", "yearbook", "zoneinfo"
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
function makeSharedPrefixObject(n) {
|
|
128
|
+
const obj = {};
|
|
129
|
+
for (let i = 0; i < n; i++) {
|
|
130
|
+
const k = `field_prefix_${String(i).padStart(4, "0")}`;
|
|
131
|
+
obj[k] = i * 10;
|
|
132
|
+
}
|
|
133
|
+
return obj;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function makeVariedKeyObject(n) {
|
|
137
|
+
const obj = {};
|
|
138
|
+
for (let i = 0; i < n; i++) {
|
|
139
|
+
const w1 = WORDS[i % WORDS.length];
|
|
140
|
+
const w2 = WORDS[Math.floor(i / WORDS.length) % WORDS.length];
|
|
141
|
+
const k = `${w1}_${i}_${w2}_attr`;
|
|
142
|
+
obj[k] = i * 10;
|
|
143
|
+
}
|
|
144
|
+
return obj;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function makeShortKeyObject(n) {
|
|
148
|
+
const obj = {};
|
|
149
|
+
for (let i = 0; i < n; i++) {
|
|
150
|
+
obj[`k${i}`] = i * 10;
|
|
151
|
+
}
|
|
152
|
+
return obj;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function makeNumberArray(n) {
|
|
156
|
+
return Array.from({ length: n }, (_, i) => i * 3);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function makeMixedNestedDoc(n) {
|
|
160
|
+
const arr = [];
|
|
161
|
+
for (let i = 0; i < n; i++) {
|
|
162
|
+
arr.push({
|
|
163
|
+
id: i,
|
|
164
|
+
username: `user_${String(i).padStart(4, "0")}`,
|
|
165
|
+
active: (i & 1) === 0,
|
|
166
|
+
score: i * 1.5,
|
|
167
|
+
meta: {
|
|
168
|
+
role: i % 3 === 0 ? "admin" : "member",
|
|
169
|
+
region: WORDS[i % 5]
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return arr;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function indexToKLiteral(idx) {
|
|
177
|
+
const bits = indexToBitPath(idx);
|
|
178
|
+
const tags = bits.slice().reverse().map((b) => `|${b}`).join("");
|
|
179
|
+
return `({}|_${tags})`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function unfoldNatValue(val) {
|
|
183
|
+
const bits = [];
|
|
184
|
+
let cur = val;
|
|
185
|
+
while (cur && typeof cur.tag === "string") {
|
|
186
|
+
if (cur.tag === "_") break;
|
|
187
|
+
bits.push(cur.tag === "1" ? 1 : 0);
|
|
188
|
+
cur = cur.value;
|
|
189
|
+
}
|
|
190
|
+
return bitPathToIndex(bits);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// -------------------------------------------------------------------
|
|
194
|
+
// Part 1: Codec & Wire Representation Benchmark
|
|
195
|
+
// -------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
const CODECS = [
|
|
198
|
+
{
|
|
199
|
+
name: "djson",
|
|
200
|
+
desc: "Sorted cons-list",
|
|
201
|
+
codec: djsonCodec,
|
|
202
|
+
pattern: djsonCodec.DJSON_PATTERN
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "djson-btrie",
|
|
206
|
+
desc: "Binary bit-trie",
|
|
207
|
+
codec: btrieCodec,
|
|
208
|
+
pattern: btrieCodec.pattern
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "djson-ptrie",
|
|
212
|
+
desc: "Patricia/Radix trie",
|
|
213
|
+
codec: ptrieCodec,
|
|
214
|
+
pattern: ptrieCodec.pattern
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "json (spec)",
|
|
218
|
+
desc: "Schema-specializing",
|
|
219
|
+
codec: jsonCodec,
|
|
220
|
+
pattern: null
|
|
221
|
+
}
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const WORKLOADS = [
|
|
225
|
+
{
|
|
226
|
+
id: "shared_obj",
|
|
227
|
+
title: "1A. Shared-Prefix Objects",
|
|
228
|
+
build: makeSharedPrefixObject
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: "varied_obj",
|
|
232
|
+
title: "1B. Varied-Key Objects",
|
|
233
|
+
build: makeVariedKeyObject
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "short_obj",
|
|
237
|
+
title: "1C. Short-Key Objects",
|
|
238
|
+
build: makeShortKeyObject
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "num_array",
|
|
242
|
+
title: "1D. Homogeneous Number Arrays",
|
|
243
|
+
build: makeNumberArray
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "mixed_doc",
|
|
247
|
+
title: "1E. Mixed Nested Documents (Array of Objects)",
|
|
248
|
+
build: makeMixedNestedDoc
|
|
249
|
+
}
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
function benchCodecOnJson(codecEntry, rawJsonObj, iters) {
|
|
253
|
+
const rawStr = JSON.stringify(rawJsonObj);
|
|
254
|
+
const jsonBytes = Buffer.byteLength(rawStr, "utf8");
|
|
255
|
+
const { codec, pattern: fixedPattern } = codecEntry;
|
|
256
|
+
|
|
257
|
+
// Warmup & correctness check
|
|
258
|
+
const kVal = codec.parse(rawStr);
|
|
259
|
+
const envPattern = fixedPattern || kVal.pattern;
|
|
260
|
+
const wireStr = encodeToWire(kVal, envPattern);
|
|
261
|
+
const decoded = decodeWire(wireStr);
|
|
262
|
+
const printedStr = codec.print(decoded.value);
|
|
263
|
+
assert.deepStrictEqual(
|
|
264
|
+
JSON.parse(printedStr),
|
|
265
|
+
rawJsonObj,
|
|
266
|
+
`${codecEntry.name} round-trip mismatch`
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const wireBytes = wireStr.length;
|
|
270
|
+
const patNodes = envPattern ? envPattern.length : 0;
|
|
271
|
+
|
|
272
|
+
// Scale iterations down for large payloads to keep runs fast
|
|
273
|
+
const effectiveIters = Math.max(
|
|
274
|
+
5,
|
|
275
|
+
Math.min(iters, Math.floor((iters * 2000) / Math.max(200, jsonBytes)))
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const tParse0 = performance.now();
|
|
279
|
+
let lastVal = kVal;
|
|
280
|
+
for (let i = 0; i < effectiveIters; i++) {
|
|
281
|
+
lastVal = codec.parse(rawStr);
|
|
282
|
+
}
|
|
283
|
+
const parseUs = ((performance.now() - tParse0) * 1000) / effectiveIters;
|
|
284
|
+
|
|
285
|
+
const tEnc0 = performance.now();
|
|
286
|
+
let lastWire = wireStr;
|
|
287
|
+
for (let i = 0; i < effectiveIters; i++) {
|
|
288
|
+
lastWire = encodeToWire(lastVal, envPattern);
|
|
289
|
+
}
|
|
290
|
+
const encodeUs = ((performance.now() - tEnc0) * 1000) / effectiveIters;
|
|
291
|
+
|
|
292
|
+
const tDec0 = performance.now();
|
|
293
|
+
let lastDec = decoded;
|
|
294
|
+
for (let i = 0; i < effectiveIters; i++) {
|
|
295
|
+
lastDec = decodeWire(lastWire);
|
|
296
|
+
}
|
|
297
|
+
const decodeUs = ((performance.now() - tDec0) * 1000) / effectiveIters;
|
|
298
|
+
|
|
299
|
+
const tPrint0 = performance.now();
|
|
300
|
+
let lastStr = printedStr;
|
|
301
|
+
for (let i = 0; i < effectiveIters; i++) {
|
|
302
|
+
lastStr = codec.print(lastDec.value);
|
|
303
|
+
}
|
|
304
|
+
const printUs = ((performance.now() - tPrint0) * 1000) / effectiveIters;
|
|
305
|
+
assert.strictEqual(lastStr.length, printedStr.length);
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
codec: codecEntry.name,
|
|
309
|
+
jsonBytes,
|
|
310
|
+
patNodes,
|
|
311
|
+
wireBytes,
|
|
312
|
+
wireRatio: wireBytes / jsonBytes,
|
|
313
|
+
parseUs,
|
|
314
|
+
encodeUs,
|
|
315
|
+
decodeUs,
|
|
316
|
+
printUs,
|
|
317
|
+
totalUs: parseUs + encodeUs + decodeUs + printUs
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function runPart1CodecBenchmarks() {
|
|
322
|
+
const results = [];
|
|
323
|
+
const bar = "=".repeat(74);
|
|
324
|
+
console.log(bar);
|
|
325
|
+
console.log("PART 1: CODEC & WIRE FORMAT BENCHMARK");
|
|
326
|
+
console.log(bar);
|
|
327
|
+
|
|
328
|
+
for (const wl of WORKLOADS) {
|
|
329
|
+
console.log(`\n### ${wl.title}`);
|
|
330
|
+
for (const n of SIZES) {
|
|
331
|
+
const data = wl.build(n);
|
|
332
|
+
const rawBytes = Buffer.byteLength(JSON.stringify(data), "utf8");
|
|
333
|
+
console.log(`\n--- Size N = ${n} (Raw JSON: ${rawBytes} B) ---`);
|
|
334
|
+
console.log(
|
|
335
|
+
"Codec | PatNodes | Wire (B) | Wire/JSON | Parse(µs) " +
|
|
336
|
+
"| Enc(µs) | Dec(µs) | Print(µs) | Total(µs)"
|
|
337
|
+
);
|
|
338
|
+
console.log(
|
|
339
|
+
"-------------+----------+----------+-----------+-----------" +
|
|
340
|
+
"+---------+---------+-----------+----------"
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
for (const c of CODECS) {
|
|
344
|
+
const r = benchCodecOnJson(c, data, CODEC_ITERS);
|
|
345
|
+
results.push({ workload: wl.id, size: n, ...r });
|
|
346
|
+
console.log(
|
|
347
|
+
`${c.name.padEnd(12)} | ${String(r.patNodes).padStart(8)} | ` +
|
|
348
|
+
`${String(r.wireBytes).padStart(8)} | ` +
|
|
349
|
+
`${(r.wireRatio.toFixed(2) + "x").padStart(9)} | ` +
|
|
350
|
+
`${r.parseUs.toFixed(1).padStart(9)} | ` +
|
|
351
|
+
`${r.encodeUs.toFixed(1).padStart(7)} | ` +
|
|
352
|
+
`${r.decodeUs.toFixed(1).padStart(7)} | ` +
|
|
353
|
+
`${r.printUs.toFixed(1).padStart(9)} | ` +
|
|
354
|
+
`${r.totalUs.toFixed(1).padStart(9)}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return results;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// -------------------------------------------------------------------
|
|
363
|
+
// Part 2: Schema Relation Execution Benchmark
|
|
364
|
+
// -------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
const SCHEMAS = [
|
|
367
|
+
{
|
|
368
|
+
name: "djson",
|
|
369
|
+
kFile: "Examples/djson.k",
|
|
370
|
+
codec: djsonCodec,
|
|
371
|
+
pattern: djsonCodec.DJSON_PATTERN,
|
|
372
|
+
fst: "/list /cons .car",
|
|
373
|
+
snd: "/list /cons .cdr /cons .car",
|
|
374
|
+
trd: "/list /cons .cdr /cons .cdr /cons .car"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "djson-btrie",
|
|
378
|
+
kFile: "Examples/djson-btrie.k",
|
|
379
|
+
codec: btrieCodec,
|
|
380
|
+
pattern: btrieCodec.pattern,
|
|
381
|
+
fst: "{ 0 index, /list list } nth",
|
|
382
|
+
snd: "{ 1 index, /list list } nth",
|
|
383
|
+
trd: "{ 0 |1 index, /list list } nth"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: "djson-ptrie",
|
|
387
|
+
kFile: "Examples/djson-ptrie.k",
|
|
388
|
+
codec: ptrieCodec,
|
|
389
|
+
pattern: ptrieCodec.pattern,
|
|
390
|
+
fst: "{ 0 index, /list list } nth",
|
|
391
|
+
snd: "{ 1 index, /list list } nth",
|
|
392
|
+
trd: "{ 0 |1 index, /list list } nth"
|
|
393
|
+
}
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
async function buildSchemaState(schema, extraDefs = "") {
|
|
397
|
+
const st = createState({ engine: "wasm" });
|
|
398
|
+
await evaluateInput(`:load ${schema.kFile}`, st);
|
|
399
|
+
const stdDefs = `
|
|
400
|
+
fst = ${schema.fst};
|
|
401
|
+
snd = ${schema.snd};
|
|
402
|
+
trd = ${schema.trd};
|
|
403
|
+
bench_dot = json? { fst /string key, snd /object object } dot json?;
|
|
404
|
+
bench_has_key = json? <
|
|
405
|
+
{
|
|
406
|
+
{ fst /string key, snd /object object } has_key? if,
|
|
407
|
+
true then
|
|
408
|
+
} .then,
|
|
409
|
+
false
|
|
410
|
+
> json?;
|
|
411
|
+
bench_obj_size = json? /object obj_size;
|
|
412
|
+
bench_list_size = json? /list list_size;
|
|
413
|
+
bench_remove = json?
|
|
414
|
+
{ fst /string key, snd /object object } remove object;
|
|
415
|
+
bench_put = json?
|
|
416
|
+
{ fst /string key, snd val, trd /object object } put object;
|
|
417
|
+
${extraDefs}
|
|
418
|
+
`;
|
|
419
|
+
await evaluateInput(stdDefs, st);
|
|
420
|
+
return st;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function compileRelationRunners(
|
|
424
|
+
state,
|
|
425
|
+
schema,
|
|
426
|
+
relName,
|
|
427
|
+
sampleInput,
|
|
428
|
+
cacheDir
|
|
429
|
+
) {
|
|
430
|
+
const prepared = prepareRelation(state, relName);
|
|
431
|
+
const wire = wireInput(sampleInput, schema.pattern);
|
|
432
|
+
|
|
433
|
+
let wasmArtifact = null;
|
|
434
|
+
let wasmRunner = null;
|
|
435
|
+
let wasmCompileMs = 0;
|
|
436
|
+
if (BACKENDS.has("wasm")) {
|
|
437
|
+
const t0 = performance.now();
|
|
438
|
+
wasmArtifact = await compileWasmArtifactFromObject(prepared.object, {
|
|
439
|
+
relation: prepared.relationName,
|
|
440
|
+
inputEnvelopePattern: wire.inputPattern,
|
|
441
|
+
usePKIR: false
|
|
442
|
+
});
|
|
443
|
+
wasmRunner = await instantiateWasmArtifact(wasmArtifact);
|
|
444
|
+
wasmCompileMs = performance.now() - t0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
let pkirArtifact = null;
|
|
448
|
+
let pkirRunner = null;
|
|
449
|
+
let pkirCompileMs = 0;
|
|
450
|
+
if (BACKENDS.has("pkir")) {
|
|
451
|
+
const t0 = performance.now();
|
|
452
|
+
pkirArtifact = await compileWasmArtifactFromObject(prepared.object, {
|
|
453
|
+
relation: prepared.relationName,
|
|
454
|
+
inputEnvelopePattern: wire.inputPattern,
|
|
455
|
+
usePKIR: true
|
|
456
|
+
});
|
|
457
|
+
pkirRunner = await instantiateWasmArtifact(pkirArtifact);
|
|
458
|
+
pkirCompileMs = performance.now() - t0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
let llvmExePath = null;
|
|
462
|
+
let llvmCompileMs = 0;
|
|
463
|
+
if (BACKENDS.has("llvm")) {
|
|
464
|
+
const t0 = performance.now();
|
|
465
|
+
llvmExePath = compileCaseExecutable({
|
|
466
|
+
object: prepared.object,
|
|
467
|
+
relationName: prepared.relationName,
|
|
468
|
+
relHash: prepared.relHash,
|
|
469
|
+
inputPattern: wire.inputPattern,
|
|
470
|
+
cacheDir,
|
|
471
|
+
sourceLabel: `${schema.name}_${relName}`,
|
|
472
|
+
runtimeMode: "fast",
|
|
473
|
+
clangOpt: "-O3",
|
|
474
|
+
usePKIR: false
|
|
475
|
+
});
|
|
476
|
+
llvmCompileMs = performance.now() - t0;
|
|
477
|
+
assert(
|
|
478
|
+
typeof llvmExePath === "string" && llvmExePath.length > 0,
|
|
479
|
+
`LLVM compilation failed for ${schema.name}:${relName}`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
state,
|
|
485
|
+
schema,
|
|
486
|
+
relName,
|
|
487
|
+
prepared,
|
|
488
|
+
wasmBytes: wasmArtifact ? wasmArtifact.length : 0,
|
|
489
|
+
pkirBytes: pkirArtifact ? pkirArtifact.length : 0,
|
|
490
|
+
wasmRunner,
|
|
491
|
+
pkirRunner,
|
|
492
|
+
llvmExePath,
|
|
493
|
+
wasmCompileMs,
|
|
494
|
+
pkirCompileMs,
|
|
495
|
+
llvmCompileMs
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function benchWasmRunner(runner, kInput, verifyFn, calls) {
|
|
500
|
+
const outVal = runner.executeValue(kInput);
|
|
501
|
+
assert(outVal !== undefined, "Wasm execution returned undefined");
|
|
502
|
+
verifyFn(outVal, "Wasm");
|
|
503
|
+
|
|
504
|
+
const ptrIn = writeValueToArena(
|
|
505
|
+
runner.exports,
|
|
506
|
+
kInput,
|
|
507
|
+
runner.inputPattern,
|
|
508
|
+
0,
|
|
509
|
+
new Map(),
|
|
510
|
+
runner.tags,
|
|
511
|
+
runner.metadata.inputPattern
|
|
512
|
+
);
|
|
513
|
+
const mark = runner.exports.arena_mark();
|
|
514
|
+
const entryFn = runner.exports[runner.metadata.entry];
|
|
515
|
+
|
|
516
|
+
// Warmup and measure single call latency
|
|
517
|
+
runner.exports.arena_reset(mark);
|
|
518
|
+
const tWarm0 = performance.now();
|
|
519
|
+
entryFn(ptrIn);
|
|
520
|
+
const singleMs = Math.max(0.001, performance.now() - tWarm0);
|
|
521
|
+
|
|
522
|
+
// Target ~50ms total execution time for timing loop
|
|
523
|
+
const effectiveCalls = Math.max(
|
|
524
|
+
10,
|
|
525
|
+
Math.min(calls, Math.floor(50 / singleMs))
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const t0 = performance.now();
|
|
529
|
+
for (let i = 0; i < effectiveCalls; i++) {
|
|
530
|
+
runner.exports.arena_reset(mark);
|
|
531
|
+
const resPtr = entryFn(ptrIn);
|
|
532
|
+
if (resPtr < 0) throw new Error("Wasm entryFn failed");
|
|
533
|
+
}
|
|
534
|
+
return ((performance.now() - t0) * 1e6) / effectiveCalls;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function executeRelationBench(compiled, kInput, verifyFn, calls) {
|
|
538
|
+
const {
|
|
539
|
+
state,
|
|
540
|
+
schema,
|
|
541
|
+
prepared,
|
|
542
|
+
wasmRunner,
|
|
543
|
+
pkirRunner,
|
|
544
|
+
llvmExePath
|
|
545
|
+
} = compiled;
|
|
546
|
+
const wire = wireInput(kInput, schema.pattern);
|
|
547
|
+
const wireBytes = wire.inputWire.length;
|
|
548
|
+
|
|
549
|
+
let kvmNs = null;
|
|
550
|
+
if (BACKENDS.has("kvm")) {
|
|
551
|
+
const ctx = {
|
|
552
|
+
rels: state.rels,
|
|
553
|
+
codes: state.codes,
|
|
554
|
+
findCode: (h) =>
|
|
555
|
+
codes.find(h) ||
|
|
556
|
+
state.codes[String(h).replace(/^@/, "")] ||
|
|
557
|
+
state.codes[h],
|
|
558
|
+
options: { envelopeFree: true }
|
|
559
|
+
};
|
|
560
|
+
const outVal = executeKVM(prepared.kvmFunc, kInput, ctx);
|
|
561
|
+
assert(outVal !== undefined, "kVM returned undefined");
|
|
562
|
+
verifyFn(outVal, "kVM");
|
|
563
|
+
|
|
564
|
+
const kvmCalls = Math.max(5, Math.min(200, Math.floor(calls / 5)));
|
|
565
|
+
for (let i = 0; i < 3; i++) {
|
|
566
|
+
executeKVM(prepared.kvmFunc, kInput, ctx);
|
|
567
|
+
}
|
|
568
|
+
const t0 = performance.now();
|
|
569
|
+
for (let i = 0; i < kvmCalls; i++) {
|
|
570
|
+
executeKVM(prepared.kvmFunc, kInput, ctx);
|
|
571
|
+
}
|
|
572
|
+
kvmNs = ((performance.now() - t0) * 1e6) / kvmCalls;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let wasmNs = null;
|
|
576
|
+
if (wasmRunner) {
|
|
577
|
+
wasmNs = benchWasmRunner(wasmRunner, kInput, verifyFn, calls);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
let pkirNs = null;
|
|
581
|
+
if (pkirRunner) {
|
|
582
|
+
pkirNs = benchWasmRunner(pkirRunner, kInput, verifyFn, calls);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let llvmNs = null;
|
|
586
|
+
if (llvmExePath) {
|
|
587
|
+
const llvmOutWire = await runExecutable(llvmExePath, wire.inputWire);
|
|
588
|
+
const llvmDecoded = decodeWire(llvmOutWire);
|
|
589
|
+
verifyFn(llvmDecoded.value, "LLVM");
|
|
590
|
+
|
|
591
|
+
const llvmCalls = Math.max(
|
|
592
|
+
10,
|
|
593
|
+
Math.min(calls, Math.floor(100_000_000 / (wasmNs || 50_000)))
|
|
594
|
+
);
|
|
595
|
+
const llvmBench = await runExecutableMainBench(
|
|
596
|
+
llvmExePath,
|
|
597
|
+
wire.inputWire,
|
|
598
|
+
llvmCalls
|
|
599
|
+
);
|
|
600
|
+
llvmNs = llvmBench.perCallNs;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
wireBytes,
|
|
605
|
+
wasmBytes: compiled.wasmBytes,
|
|
606
|
+
pkirBytes: compiled.pkirBytes,
|
|
607
|
+
wasmCompileMs: compiled.wasmCompileMs,
|
|
608
|
+
llvmCompileMs: compiled.llvmCompileMs,
|
|
609
|
+
kvmNs,
|
|
610
|
+
wasmNs,
|
|
611
|
+
pkirNs,
|
|
612
|
+
llvmNs
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function formatNs(ns) {
|
|
617
|
+
if (ns === null || ns === undefined) return " n/a";
|
|
618
|
+
if (ns >= 1e6) return `${(ns / 1e6).toFixed(2).padStart(6)}ms`;
|
|
619
|
+
if (ns >= 1e4) return `${(ns / 1e3).toFixed(1).padStart(6)}µs`;
|
|
620
|
+
return `${ns.toFixed(0).padStart(6)}ns`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function formatSpeedup(baseNs, curNs) {
|
|
624
|
+
if (!baseNs || !curNs) return " n/a";
|
|
625
|
+
const ratio = baseNs / curNs;
|
|
626
|
+
return `${ratio.toFixed(1).padStart(5)}x`;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function printRelationTableHeader() {
|
|
630
|
+
console.log(
|
|
631
|
+
"Schema | Wire (B) | Wasm (B) | kVM | Wasm | " +
|
|
632
|
+
"PKIR | LLVM -O3 | Wasm Spd | LLVM Spd"
|
|
633
|
+
);
|
|
634
|
+
console.log(
|
|
635
|
+
"-------------+----------+----------+----------+----------+---" +
|
|
636
|
+
"-------+----------+----------+---------"
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function printRelationRow(schemaName, res, baseRes) {
|
|
641
|
+
const wasmSpd = baseRes
|
|
642
|
+
? formatSpeedup(baseRes.wasmNs, res.wasmNs)
|
|
643
|
+
: " 1.0x";
|
|
644
|
+
const llvmSpd = baseRes
|
|
645
|
+
? formatSpeedup(baseRes.llvmNs, res.llvmNs)
|
|
646
|
+
: " 1.0x";
|
|
647
|
+
console.log(
|
|
648
|
+
`${schemaName.padEnd(12)} | ${String(res.wireBytes).padStart(8)} | ` +
|
|
649
|
+
`${String(res.wasmBytes).padStart(8)} | ` +
|
|
650
|
+
`${formatNs(res.kvmNs)} | ` +
|
|
651
|
+
`${formatNs(res.wasmNs)} | ` +
|
|
652
|
+
`${formatNs(res.pkirNs)} | ` +
|
|
653
|
+
`${formatNs(res.llvmNs)} | ` +
|
|
654
|
+
`${wasmSpd.padStart(8)} | ` +
|
|
655
|
+
`${llvmSpd.padStart(8)}`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function runPart2SchemaBenchmarks() {
|
|
660
|
+
const cacheDir = fs.mkdtempSync(
|
|
661
|
+
path.join(os.tmpdir(), "k-perf-djson-")
|
|
662
|
+
);
|
|
663
|
+
const results = [];
|
|
664
|
+
|
|
665
|
+
try {
|
|
666
|
+
const bar = "=".repeat(74);
|
|
667
|
+
console.log(`\n${bar}`);
|
|
668
|
+
console.log("PART 2: SCHEMA RELATION EXECUTION BENCHMARK");
|
|
669
|
+
console.log(bar);
|
|
670
|
+
|
|
671
|
+
// Build extra nth definitions for each size N
|
|
672
|
+
const nthTargets = new Map();
|
|
673
|
+
for (const n of SIZES) {
|
|
674
|
+
nthTargets.set(`nth_first_${n}`, 0);
|
|
675
|
+
nthTargets.set(`nth_mid_${n}`, Math.floor(n / 2));
|
|
676
|
+
nthTargets.set(`nth_last_${n}`, n - 1);
|
|
677
|
+
}
|
|
678
|
+
const extraNthDefs = Array.from(nthTargets.entries())
|
|
679
|
+
.map(
|
|
680
|
+
([relName, idx]) =>
|
|
681
|
+
`${relName} = json? { ` +
|
|
682
|
+
`${indexToKLiteral(idx)} index, /list list } nth json?;`
|
|
683
|
+
)
|
|
684
|
+
.join("\n");
|
|
685
|
+
|
|
686
|
+
console.log(
|
|
687
|
+
"\n==> Compiling schema relations (Wasm, PKIR, LLVM -O3)..."
|
|
688
|
+
);
|
|
689
|
+
const compiledBySchema = new Map();
|
|
690
|
+
const samplePair = ["alpha", { alpha: 1, beta: 2 }];
|
|
691
|
+
const sampleTriple = ["gamma", 99, { alpha: 1, beta: 2 }];
|
|
692
|
+
const sampleObj = { alpha: 1, beta: 2 };
|
|
693
|
+
const sampleArr = [10, 20, 30];
|
|
694
|
+
|
|
695
|
+
for (const s of SCHEMAS) {
|
|
696
|
+
const st = await buildSchemaState(s, extraNthDefs);
|
|
697
|
+
const map = new Map();
|
|
698
|
+
map.set(
|
|
699
|
+
"bench_dot",
|
|
700
|
+
await compileRelationRunners(
|
|
701
|
+
st,
|
|
702
|
+
s,
|
|
703
|
+
"bench_dot",
|
|
704
|
+
s.codec.parse(JSON.stringify(samplePair)),
|
|
705
|
+
cacheDir
|
|
706
|
+
)
|
|
707
|
+
);
|
|
708
|
+
map.set(
|
|
709
|
+
"bench_has_key",
|
|
710
|
+
await compileRelationRunners(
|
|
711
|
+
st,
|
|
712
|
+
s,
|
|
713
|
+
"bench_has_key",
|
|
714
|
+
s.codec.parse(JSON.stringify(samplePair)),
|
|
715
|
+
cacheDir
|
|
716
|
+
)
|
|
717
|
+
);
|
|
718
|
+
map.set(
|
|
719
|
+
"bench_obj_size",
|
|
720
|
+
await compileRelationRunners(
|
|
721
|
+
st,
|
|
722
|
+
s,
|
|
723
|
+
"bench_obj_size",
|
|
724
|
+
s.codec.parse(JSON.stringify(sampleObj)),
|
|
725
|
+
cacheDir
|
|
726
|
+
)
|
|
727
|
+
);
|
|
728
|
+
map.set(
|
|
729
|
+
"bench_list_size",
|
|
730
|
+
await compileRelationRunners(
|
|
731
|
+
st,
|
|
732
|
+
s,
|
|
733
|
+
"bench_list_size",
|
|
734
|
+
s.codec.parse(JSON.stringify(sampleArr)),
|
|
735
|
+
cacheDir
|
|
736
|
+
)
|
|
737
|
+
);
|
|
738
|
+
map.set(
|
|
739
|
+
"bench_remove",
|
|
740
|
+
await compileRelationRunners(
|
|
741
|
+
st,
|
|
742
|
+
s,
|
|
743
|
+
"bench_remove",
|
|
744
|
+
s.codec.parse(JSON.stringify(samplePair)),
|
|
745
|
+
cacheDir
|
|
746
|
+
)
|
|
747
|
+
);
|
|
748
|
+
map.set(
|
|
749
|
+
"bench_put",
|
|
750
|
+
await compileRelationRunners(
|
|
751
|
+
st,
|
|
752
|
+
s,
|
|
753
|
+
"bench_put",
|
|
754
|
+
s.codec.parse(JSON.stringify(sampleTriple)),
|
|
755
|
+
cacheDir
|
|
756
|
+
)
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
for (const n of SIZES) {
|
|
760
|
+
const arrN = makeNumberArray(n);
|
|
761
|
+
const kArrN = s.codec.parse(JSON.stringify(arrN));
|
|
762
|
+
for (const pos of ["first", "mid", "last"]) {
|
|
763
|
+
const relName = `nth_${pos}_${n}`;
|
|
764
|
+
map.set(
|
|
765
|
+
relName,
|
|
766
|
+
await compileRelationRunners(st, s, relName, kArrN, cacheDir)
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
compiledBySchema.set(s.name, map);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// ---------------------------------------------------------------
|
|
774
|
+
// 2A. Object Lookup (`dot`) — Shared-Prefix Keys
|
|
775
|
+
// ---------------------------------------------------------------
|
|
776
|
+
console.log("\n### 2A. Object Lookup (`dot`) — Shared-Prefix Keys");
|
|
777
|
+
for (const n of SIZES) {
|
|
778
|
+
const obj = makeSharedPrefixObject(n);
|
|
779
|
+
const keys = Object.keys(obj).sort();
|
|
780
|
+
const queries = [
|
|
781
|
+
["first", keys[0]],
|
|
782
|
+
["mid", keys[Math.floor(n / 2)]],
|
|
783
|
+
["last", keys[n - 1]]
|
|
784
|
+
];
|
|
785
|
+
for (const [posLabel, targetKey] of queries) {
|
|
786
|
+
const expectedVal = obj[targetKey];
|
|
787
|
+
console.log(
|
|
788
|
+
`\n--- Shared-Prefix Object N = ${n}, ` +
|
|
789
|
+
`key = ${posLabel} ('${targetKey}') ---`
|
|
790
|
+
);
|
|
791
|
+
printRelationTableHeader();
|
|
792
|
+
let baseRes = null;
|
|
793
|
+
for (const s of SCHEMAS) {
|
|
794
|
+
const compiled = compiledBySchema.get(s.name).get("bench_dot");
|
|
795
|
+
const kInput = s.codec.parse(JSON.stringify([targetKey, obj]));
|
|
796
|
+
const res = await executeRelationBench(
|
|
797
|
+
compiled,
|
|
798
|
+
kInput,
|
|
799
|
+
(val, backend) => {
|
|
800
|
+
assert.strictEqual(
|
|
801
|
+
JSON.parse(s.codec.print(val)),
|
|
802
|
+
expectedVal,
|
|
803
|
+
`${s.name} (${backend}) dot mismatch`
|
|
804
|
+
);
|
|
805
|
+
},
|
|
806
|
+
CALLS
|
|
807
|
+
);
|
|
808
|
+
if (!baseRes) baseRes = res;
|
|
809
|
+
printRelationRow(s.name, res, baseRes);
|
|
810
|
+
results.push({
|
|
811
|
+
section: "dot_shared",
|
|
812
|
+
size: n,
|
|
813
|
+
position: posLabel,
|
|
814
|
+
schema: s.name,
|
|
815
|
+
...res
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// ---------------------------------------------------------------
|
|
822
|
+
// 2B. Object Lookup (`dot`) — Varied Keys
|
|
823
|
+
// ---------------------------------------------------------------
|
|
824
|
+
console.log("\n### 2B. Object Lookup (`dot`) — Varied Keys");
|
|
825
|
+
for (const n of SIZES) {
|
|
826
|
+
const obj = makeVariedKeyObject(n);
|
|
827
|
+
const keys = Object.keys(obj).sort();
|
|
828
|
+
const queries = [
|
|
829
|
+
["first", keys[0]],
|
|
830
|
+
["mid", keys[Math.floor(n / 2)]],
|
|
831
|
+
["last", keys[n - 1]]
|
|
832
|
+
];
|
|
833
|
+
for (const [posLabel, targetKey] of queries) {
|
|
834
|
+
const expectedVal = obj[targetKey];
|
|
835
|
+
console.log(
|
|
836
|
+
`\n--- Varied-Key Object N = ${n}, ` +
|
|
837
|
+
`key = ${posLabel} ('${targetKey}') ---`
|
|
838
|
+
);
|
|
839
|
+
printRelationTableHeader();
|
|
840
|
+
let baseRes = null;
|
|
841
|
+
for (const s of SCHEMAS) {
|
|
842
|
+
const compiled = compiledBySchema.get(s.name).get("bench_dot");
|
|
843
|
+
const kInput = s.codec.parse(JSON.stringify([targetKey, obj]));
|
|
844
|
+
const res = await executeRelationBench(
|
|
845
|
+
compiled,
|
|
846
|
+
kInput,
|
|
847
|
+
(val, backend) => {
|
|
848
|
+
assert.strictEqual(
|
|
849
|
+
JSON.parse(s.codec.print(val)),
|
|
850
|
+
expectedVal,
|
|
851
|
+
`${s.name} (${backend}) dot mismatch`
|
|
852
|
+
);
|
|
853
|
+
},
|
|
854
|
+
CALLS
|
|
855
|
+
);
|
|
856
|
+
if (!baseRes) baseRes = res;
|
|
857
|
+
printRelationRow(s.name, res, baseRes);
|
|
858
|
+
results.push({
|
|
859
|
+
section: "dot_varied",
|
|
860
|
+
size: n,
|
|
861
|
+
position: posLabel,
|
|
862
|
+
schema: s.name,
|
|
863
|
+
...res
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// ---------------------------------------------------------------
|
|
870
|
+
// 2C. Key Membership Filter (`has_key?`) — Hit vs Miss
|
|
871
|
+
// ---------------------------------------------------------------
|
|
872
|
+
console.log(
|
|
873
|
+
"\n### 2C. Object Membership (`has_key?`) — Hit (last) vs Miss"
|
|
874
|
+
);
|
|
875
|
+
for (const n of SIZES) {
|
|
876
|
+
const obj = makeVariedKeyObject(n);
|
|
877
|
+
const keys = Object.keys(obj).sort();
|
|
878
|
+
const cases = [
|
|
879
|
+
["hit", keys[n - 1], true],
|
|
880
|
+
["miss", "zzzz_nonexistent_key", false]
|
|
881
|
+
];
|
|
882
|
+
for (const [caseLabel, queryKey, expectedBool] of cases) {
|
|
883
|
+
console.log(
|
|
884
|
+
`\n--- Varied-Key Object N = ${n}, ` +
|
|
885
|
+
`case = ${caseLabel} ('${queryKey}') ---`
|
|
886
|
+
);
|
|
887
|
+
printRelationTableHeader();
|
|
888
|
+
let baseRes = null;
|
|
889
|
+
for (const s of SCHEMAS) {
|
|
890
|
+
const compiled = compiledBySchema
|
|
891
|
+
.get(s.name)
|
|
892
|
+
.get("bench_has_key");
|
|
893
|
+
const kInput = s.codec.parse(JSON.stringify([queryKey, obj]));
|
|
894
|
+
const res = await executeRelationBench(
|
|
895
|
+
compiled,
|
|
896
|
+
kInput,
|
|
897
|
+
(val, backend) => {
|
|
898
|
+
assert.strictEqual(
|
|
899
|
+
JSON.parse(s.codec.print(val)),
|
|
900
|
+
expectedBool,
|
|
901
|
+
`${s.name} (${backend}) has_key? mismatch`
|
|
902
|
+
);
|
|
903
|
+
},
|
|
904
|
+
CALLS
|
|
905
|
+
);
|
|
906
|
+
if (!baseRes) baseRes = res;
|
|
907
|
+
printRelationRow(s.name, res, baseRes);
|
|
908
|
+
results.push({
|
|
909
|
+
section: "has_key",
|
|
910
|
+
size: n,
|
|
911
|
+
position: caseLabel,
|
|
912
|
+
schema: s.name,
|
|
913
|
+
...res
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// ---------------------------------------------------------------
|
|
920
|
+
// 2D. Array Element Lookup (`nth`) — First, Middle, Last Index
|
|
921
|
+
// ---------------------------------------------------------------
|
|
922
|
+
console.log(
|
|
923
|
+
"\n### 2D. Array Element Lookup (`nth`) — First, Mid, Last Index"
|
|
924
|
+
);
|
|
925
|
+
for (const n of SIZES) {
|
|
926
|
+
const arr = makeNumberArray(n);
|
|
927
|
+
const positions = [
|
|
928
|
+
["first", 0],
|
|
929
|
+
["mid", Math.floor(n / 2)],
|
|
930
|
+
["last", n - 1]
|
|
931
|
+
];
|
|
932
|
+
for (const [posLabel, targetIdx] of positions) {
|
|
933
|
+
const expectedVal = arr[targetIdx];
|
|
934
|
+
const relName = `nth_${posLabel}_${n}`;
|
|
935
|
+
console.log(
|
|
936
|
+
`\n--- Array N = ${n}, index = ${posLabel} (${targetIdx}) ---`
|
|
937
|
+
);
|
|
938
|
+
printRelationTableHeader();
|
|
939
|
+
let baseRes = null;
|
|
940
|
+
for (const s of SCHEMAS) {
|
|
941
|
+
const compiled = compiledBySchema.get(s.name).get(relName);
|
|
942
|
+
const kInput = s.codec.parse(JSON.stringify(arr));
|
|
943
|
+
const res = await executeRelationBench(
|
|
944
|
+
compiled,
|
|
945
|
+
kInput,
|
|
946
|
+
(val, backend) => {
|
|
947
|
+
assert.strictEqual(
|
|
948
|
+
JSON.parse(s.codec.print(val)),
|
|
949
|
+
expectedVal,
|
|
950
|
+
`${s.name} (${backend}) nth mismatch`
|
|
951
|
+
);
|
|
952
|
+
},
|
|
953
|
+
CALLS
|
|
954
|
+
);
|
|
955
|
+
if (!baseRes) baseRes = res;
|
|
956
|
+
printRelationRow(s.name, res, baseRes);
|
|
957
|
+
results.push({
|
|
958
|
+
section: "nth",
|
|
959
|
+
size: n,
|
|
960
|
+
position: posLabel,
|
|
961
|
+
schema: s.name,
|
|
962
|
+
...res
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// ---------------------------------------------------------------
|
|
969
|
+
// 2E. Subtree Cardinality Queries (`obj_size` and `list_size`)
|
|
970
|
+
// ---------------------------------------------------------------
|
|
971
|
+
console.log(
|
|
972
|
+
"\n### 2E. Cardinality Queries (`obj_size` & `list_size`: " +
|
|
973
|
+
"O(N) vs O(1) in djson-ptrie)"
|
|
974
|
+
);
|
|
975
|
+
for (const n of SIZES) {
|
|
976
|
+
const obj = makeVariedKeyObject(n);
|
|
977
|
+
console.log(`\n--- Object Size Query (obj_size) for N = ${n} ---`);
|
|
978
|
+
printRelationTableHeader();
|
|
979
|
+
let baseObjRes = null;
|
|
980
|
+
for (const s of SCHEMAS) {
|
|
981
|
+
const compiled = compiledBySchema
|
|
982
|
+
.get(s.name)
|
|
983
|
+
.get("bench_obj_size");
|
|
984
|
+
const kInput = s.codec.parse(JSON.stringify(obj));
|
|
985
|
+
const res = await executeRelationBench(
|
|
986
|
+
compiled,
|
|
987
|
+
kInput,
|
|
988
|
+
(val, backend) => {
|
|
989
|
+
assert.strictEqual(
|
|
990
|
+
unfoldNatValue(val),
|
|
991
|
+
n,
|
|
992
|
+
`${s.name} (${backend}) obj_size mismatch`
|
|
993
|
+
);
|
|
994
|
+
},
|
|
995
|
+
CALLS
|
|
996
|
+
);
|
|
997
|
+
if (!baseObjRes) baseObjRes = res;
|
|
998
|
+
printRelationRow(s.name, res, baseObjRes);
|
|
999
|
+
results.push({
|
|
1000
|
+
section: "obj_size",
|
|
1001
|
+
size: n,
|
|
1002
|
+
schema: s.name,
|
|
1003
|
+
...res
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const arr = makeNumberArray(n);
|
|
1008
|
+
console.log(`\n--- Array Size Query (list_size) for N = ${n} ---`);
|
|
1009
|
+
printRelationTableHeader();
|
|
1010
|
+
let baseListRes = null;
|
|
1011
|
+
for (const s of SCHEMAS) {
|
|
1012
|
+
const compiled = compiledBySchema
|
|
1013
|
+
.get(s.name)
|
|
1014
|
+
.get("bench_list_size");
|
|
1015
|
+
const kInput = s.codec.parse(JSON.stringify(arr));
|
|
1016
|
+
const res = await executeRelationBench(
|
|
1017
|
+
compiled,
|
|
1018
|
+
kInput,
|
|
1019
|
+
(val, backend) => {
|
|
1020
|
+
assert.strictEqual(
|
|
1021
|
+
unfoldNatValue(val),
|
|
1022
|
+
n,
|
|
1023
|
+
`${s.name} (${backend}) list_size mismatch`
|
|
1024
|
+
);
|
|
1025
|
+
},
|
|
1026
|
+
CALLS
|
|
1027
|
+
);
|
|
1028
|
+
if (!baseListRes) baseListRes = res;
|
|
1029
|
+
printRelationRow(s.name, res, baseListRes);
|
|
1030
|
+
results.push({
|
|
1031
|
+
section: "list_size",
|
|
1032
|
+
size: n,
|
|
1033
|
+
schema: s.name,
|
|
1034
|
+
...res
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// ---------------------------------------------------------------
|
|
1040
|
+
// 2F. Object Key Removal (`remove`) and Insertion (`put`)
|
|
1041
|
+
// ---------------------------------------------------------------
|
|
1042
|
+
console.log("\n### 2F. Object Update Operations (`remove` & `put`)");
|
|
1043
|
+
for (const n of SIZES) {
|
|
1044
|
+
const obj = makeVariedKeyObject(n);
|
|
1045
|
+
const keys = Object.keys(obj).sort();
|
|
1046
|
+
const targetKey = keys[n - 1];
|
|
1047
|
+
const expectedAfterRem = { ...obj };
|
|
1048
|
+
delete expectedAfterRem[targetKey];
|
|
1049
|
+
|
|
1050
|
+
console.log(
|
|
1051
|
+
`\n--- Object Key Removal (remove '${targetKey}') ` +
|
|
1052
|
+
`for N = ${n} ---`
|
|
1053
|
+
);
|
|
1054
|
+
printRelationTableHeader();
|
|
1055
|
+
let baseRemRes = null;
|
|
1056
|
+
for (const s of SCHEMAS) {
|
|
1057
|
+
const compiled = compiledBySchema
|
|
1058
|
+
.get(s.name)
|
|
1059
|
+
.get("bench_remove");
|
|
1060
|
+
const kInput = s.codec.parse(JSON.stringify([targetKey, obj]));
|
|
1061
|
+
const res = await executeRelationBench(
|
|
1062
|
+
compiled,
|
|
1063
|
+
kInput,
|
|
1064
|
+
(val, backend) => {
|
|
1065
|
+
assert.deepStrictEqual(
|
|
1066
|
+
JSON.parse(s.codec.print(val)),
|
|
1067
|
+
expectedAfterRem,
|
|
1068
|
+
`${s.name} (${backend}) remove mismatch`
|
|
1069
|
+
);
|
|
1070
|
+
},
|
|
1071
|
+
CALLS
|
|
1072
|
+
);
|
|
1073
|
+
if (!baseRemRes) baseRemRes = res;
|
|
1074
|
+
printRelationRow(s.name, res, baseRemRes);
|
|
1075
|
+
results.push({
|
|
1076
|
+
section: "remove",
|
|
1077
|
+
size: n,
|
|
1078
|
+
schema: s.name,
|
|
1079
|
+
...res
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const newKey = " middle_inserted_key_attr";
|
|
1084
|
+
const newVal = 777;
|
|
1085
|
+
const expectedAfterPut = { ...obj, [newKey]: newVal };
|
|
1086
|
+
console.log(
|
|
1087
|
+
`\n--- Object Key Insertion (put '${newKey}') for N = ${n} ---`
|
|
1088
|
+
);
|
|
1089
|
+
printRelationTableHeader();
|
|
1090
|
+
let basePutRes = null;
|
|
1091
|
+
for (const s of SCHEMAS) {
|
|
1092
|
+
const compiled = compiledBySchema
|
|
1093
|
+
.get(s.name)
|
|
1094
|
+
.get("bench_put");
|
|
1095
|
+
const kInput = s.codec.parse(
|
|
1096
|
+
JSON.stringify([newKey, newVal, obj])
|
|
1097
|
+
);
|
|
1098
|
+
const res = await executeRelationBench(
|
|
1099
|
+
compiled,
|
|
1100
|
+
kInput,
|
|
1101
|
+
(val, backend) => {
|
|
1102
|
+
assert.deepStrictEqual(
|
|
1103
|
+
JSON.parse(s.codec.print(val)),
|
|
1104
|
+
expectedAfterPut,
|
|
1105
|
+
`${s.name} (${backend}) put mismatch`
|
|
1106
|
+
);
|
|
1107
|
+
},
|
|
1108
|
+
CALLS
|
|
1109
|
+
);
|
|
1110
|
+
if (!basePutRes) basePutRes = res;
|
|
1111
|
+
printRelationRow(s.name, res, basePutRes);
|
|
1112
|
+
results.push({
|
|
1113
|
+
section: "put",
|
|
1114
|
+
size: n,
|
|
1115
|
+
schema: s.name,
|
|
1116
|
+
...res
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
} finally {
|
|
1121
|
+
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
return results;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// -------------------------------------------------------------------
|
|
1128
|
+
// Main Runner
|
|
1129
|
+
// -------------------------------------------------------------------
|
|
1130
|
+
|
|
1131
|
+
const summaryReport = {};
|
|
1132
|
+
if (runCodecsPart) {
|
|
1133
|
+
summaryReport.codecs = runPart1CodecBenchmarks();
|
|
1134
|
+
}
|
|
1135
|
+
if (runSchemasPart) {
|
|
1136
|
+
summaryReport.schemas = await runPart2SchemaBenchmarks();
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (emitJson) {
|
|
1140
|
+
console.log("\n=== JSON REPORT ===");
|
|
1141
|
+
console.log(JSON.stringify(summaryReport, null, 2));
|
|
1142
|
+
}
|