import fs from 'node:fs'; import path from 'node:path'; import { performance } from 'node:perf_hooks'; import { CondensedGraph } from '../src/core/CondensedGraph.js'; import { ShardedSnapshotBuilder } from '../src/core/shards/ShardedSnapshotBuilder.js'; import { ShardedSnapshot } from '../src/core/shards/ShardedSnapshot.js'; import { FileShardStorage } from '../src/core/shards/FileShardStorage.js'; function parseArgs(argv) { const args = new Map(); for (let i = 2; i < argv.length; i++) { const value = argv[i]; if (!value.startsWith('--')) continue; const [key, inline] = value.slice(2).split('='); if (inline !== undefined) { args.set(key, inline); continue; } const next = argv[i + 1]; if (next && !next.startsWith('--')) { args.set(key, next); i++; } else { args.set(key, true); } } return args; } const args = parseArgs(process.argv); const edges = Number(args.get('edges') || 50000); const users = Number(args.get('users') || 5000); const docs = Number(args.get('docs') || 20000); const bucketSize = Number(args.get('bucket') || 4096); const samples = Number(args.get('samples') || 10000); const output = args.get('output') || 'tmp/shards-bench'; console.log('Sharded snapshot bench'); console.log(` edges: ${edges}`); console.log(` users: ${users}`); console.log(` docs: ${docs}`); console.log(` bucket: ${bucketSize}`); console.log(` samples: ${samples}`); const graph = new CondensedGraph(); const relations = ['owner', 'editor', 'viewer']; const edgePairs = []; const buildStart = performance.now(); for (let i = 0; i < edges; i++) { const userIndex = i % users; const docIndex = i % docs; const relId = i % 3; const srcId = graph._ensureNode(`user:${userIndex}`); const dstId = graph._ensureNode(`doc:${docIndex}`); graph.addEdge(srcId, relations[relId], dstId); edgePairs.push({ srcId, dstId, relId }); } const buildTime = performance.now() - buildStart; graph.finalizePerfectHash(); graph.finalizeWaveletAdjacency({ dropAdjacencyList: true }); const snapshot = graph.toBinary(); const snapshotPath = path.join(output, 'snapshot.bin'); fs.mkdirSync(output, { recursive: true }); fs.writeFileSync(snapshotPath, new Uint8Array(snapshot)); const shardStart = performance.now(); const builder = new ShardedSnapshotBuilder({ bucketSize, includeDirections: ['out'] }); const manifest = builder.build(graph, output); const shardTime = performance.now() - shardStart; const storage = new FileShardStorage(output); const sharded = new ShardedSnapshot(manifest, storage, { cacheLimit: 8 }); sharded.initializeSync(); const srcIds = Array.from({ length: users }, (_, i) => i); const dstIds = Array.from({ length: docs }, (_, i) => i + users); const findStart = performance.now(); let hits = 0; for (let i = 0; i < samples; i++) { const pair = edgePairs[i % edgePairs.length]; const srcId = pair.srcId; const dstId = pair.dstId; const relId = pair.relId; const edge = sharded.findEdgeSync(srcId, relId, dstId); if (edge) hits++; } const findTime = performance.now() - findStart; const outStart = performance.now(); let outEdges = 0; for (let i = 0; i < samples; i++) { const srcId = srcIds[i % srcIds.length]; const relId = i % 3; const edgesOut = sharded.getOutEdgesSync(srcId, relId); outEdges += edgesOut.length; } const outTime = performance.now() - outStart; console.log('\nResults'); console.log(` build time: ${buildTime.toFixed(2)} ms`); console.log(` shard time: ${shardTime.toFixed(2)} ms`); console.log(` shards: ${manifest.shards.length}`); console.log(` snapshot size: ${(snapshot.byteLength / 1024 / 1024).toFixed(2)} MB`); console.log(` findEdge avg: ${(findTime / samples * 1000).toFixed(3)} µs`); console.log(` getOutEdges avg: ${(outTime / samples * 1000).toFixed(3)} µs`); console.log(` hits: ${hits}`); console.log(` outEdges: ${outEdges}`);