|
| 1 | +import { performance } from "node:perf_hooks"; |
| 2 | + |
| 3 | +import nxpp from "../dist/index.js"; |
| 4 | + |
| 5 | +function readIntArg(name, fallback) { |
| 6 | + const index = process.argv.indexOf(name); |
| 7 | + if (index === -1 || index + 1 >= process.argv.length) { |
| 8 | + return fallback; |
| 9 | + } |
| 10 | + const value = Number.parseInt(process.argv[index + 1], 10); |
| 11 | + return Number.isFinite(value) ? value : fallback; |
| 12 | +} |
| 13 | + |
| 14 | +const config = { |
| 15 | + nodes: readIntArg("--nodes", 300), |
| 16 | + edges: readIntArg("--edges", 900), |
| 17 | + floydNodes: readIntArg("--floyd-nodes", 35), |
| 18 | + attrOps: readIntArg("--attr-ops", 1000), |
| 19 | + multigraphEdges: readIntArg("--multigraph-edges", 600), |
| 20 | + iterations: readIntArg("--iterations", 5), |
| 21 | +}; |
| 22 | + |
| 23 | +function consume(value) { |
| 24 | + globalThis.__nxppBenchmarkSink = value; |
| 25 | +} |
| 26 | + |
| 27 | +function toArray(value) { |
| 28 | + return Array.from(value); |
| 29 | +} |
| 30 | + |
| 31 | +function measureMs(iterations, fn) { |
| 32 | + const start = performance.now(); |
| 33 | + for (let i = 0; i < iterations; i += 1) { |
| 34 | + fn(); |
| 35 | + } |
| 36 | + return performance.now() - start; |
| 37 | +} |
| 38 | + |
| 39 | +function printHeader() { |
| 40 | + console.log("layer,workload,iterations,total_ms,ops_per_second,notes"); |
| 41 | +} |
| 42 | + |
| 43 | +function printRow(layer, workload, iterations, totalMs, notes) { |
| 44 | + const opsPerSecond = totalMs > 0 ? (iterations * 1000) / totalMs : 0; |
| 45 | + console.log([ |
| 46 | + layer, |
| 47 | + workload, |
| 48 | + iterations, |
| 49 | + totalMs.toFixed(3), |
| 50 | + opsPerSecond.toFixed(3), |
| 51 | + notes, |
| 52 | + ].join(",")); |
| 53 | +} |
| 54 | + |
| 55 | +function makeDiGraph(GraphCtor, nodes = config.nodes, edges = config.edges) { |
| 56 | + const graph = new GraphCtor(); |
| 57 | + for (let node = 0; node < nodes; node += 1) { |
| 58 | + graph.addNode(node); |
| 59 | + } |
| 60 | + for (let edge = 0; edge < edges; edge += 1) { |
| 61 | + const source = edge % nodes; |
| 62 | + let target = (edge * 37 + 11) % nodes; |
| 63 | + if (target === source) { |
| 64 | + target = (target + 1) % nodes; |
| 65 | + } |
| 66 | + graph.addEdge(source, target, (edge % 17) + 1); |
| 67 | + } |
| 68 | + return graph; |
| 69 | +} |
| 70 | + |
| 71 | +function makeFloydGraph(GraphCtor) { |
| 72 | + const graph = new GraphCtor(); |
| 73 | + for (let node = 0; node < config.floydNodes; node += 1) { |
| 74 | + graph.addNode(node); |
| 75 | + } |
| 76 | + for (let node = 0; node + 1 < config.floydNodes; node += 1) { |
| 77 | + graph.addEdge(node, node + 1, 1); |
| 78 | + if (node + 3 < config.floydNodes) { |
| 79 | + graph.addEdge(node, node + 3, 4); |
| 80 | + } |
| 81 | + } |
| 82 | + return graph; |
| 83 | +} |
| 84 | + |
| 85 | +function dispose(graph) { |
| 86 | + if (typeof graph.dispose === "function") { |
| 87 | + graph.dispose(); |
| 88 | + return; |
| 89 | + } |
| 90 | + if (typeof graph.delete === "function") { |
| 91 | + graph.delete(); |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +function runLayer(layer, constructors) { |
| 96 | + const constructionMs = measureMs(config.iterations, () => { |
| 97 | + const graph = makeDiGraph(constructors.DiGraphInt); |
| 98 | + consume(toArray(graph.nodes()).length); |
| 99 | + dispose(graph); |
| 100 | + }); |
| 101 | + printRow(layer, "construct_digraph", config.iterations, constructionMs, `nodes=${config.nodes};edges=${config.edges}`); |
| 102 | + |
| 103 | + const graph = makeDiGraph(constructors.DiGraphInt); |
| 104 | + const bfsMs = measureMs(config.iterations, () => { |
| 105 | + consume(toArray(graph.bfsEdges(0)).length); |
| 106 | + }); |
| 107 | + printRow(layer, "bfs_edges", config.iterations, bfsMs, "source=0"); |
| 108 | + |
| 109 | + const dijkstraMs = measureMs(config.iterations, () => { |
| 110 | + const result = graph.dijkstraShortestPaths(0); |
| 111 | + consume(toArray(result.distance).length); |
| 112 | + }); |
| 113 | + printRow(layer, "dijkstra_shortest_paths", config.iterations, dijkstraMs, "source=0"); |
| 114 | + dispose(graph); |
| 115 | + |
| 116 | + const floydGraph = makeFloydGraph(constructors.DiGraphInt); |
| 117 | + const floydMs = measureMs(config.iterations, () => { |
| 118 | + consume(toArray(floydGraph.floydWarshallAllPairsShortestPaths()).length); |
| 119 | + }); |
| 120 | + printRow(layer, "floyd_warshall_all_pairs", config.iterations, floydMs, `nodes=${config.floydNodes}`); |
| 121 | + dispose(floydGraph); |
| 122 | + |
| 123 | + const attrGraph = new constructors.DiGraphInt(); |
| 124 | + const attrMs = measureMs(config.iterations, () => { |
| 125 | + for (let i = 0; i < config.attrOps; i += 1) { |
| 126 | + const node = i % config.nodes; |
| 127 | + attrGraph.setNodeAttr(node, "label", `node-${node}`); |
| 128 | + consume(attrGraph.getNodeAttr(node, "label")); |
| 129 | + } |
| 130 | + }); |
| 131 | + printRow(layer, "attribute_roundtrip", config.iterations, attrMs, `ops_per_iteration=${config.attrOps}`); |
| 132 | + dispose(attrGraph); |
| 133 | + |
| 134 | + const multigraphMs = measureMs(config.iterations, () => { |
| 135 | + const multigraph = new constructors.MultiDiGraphInt(); |
| 136 | + for (let i = 0; i < config.multigraphEdges; i += 1) { |
| 137 | + multigraph.addEdge(i % config.nodes, (i + 1) % config.nodes, 1); |
| 138 | + } |
| 139 | + const ids = toArray(multigraph.edgeIdsBetween(0, 1)); |
| 140 | + for (const id of ids) { |
| 141 | + multigraph.setEdgeAttrById(id, "capacity", id + 1); |
| 142 | + } |
| 143 | + consume(ids.length); |
| 144 | + dispose(multigraph); |
| 145 | + }); |
| 146 | + printRow(layer, "multigraph_edge_ids", config.iterations, multigraphMs, `edges=${config.multigraphEdges}`); |
| 147 | +} |
| 148 | + |
| 149 | +printHeader(); |
| 150 | +const rawRuntime = await nxpp.createNxpp(); |
| 151 | +runLayer("raw_wasm", rawRuntime); |
| 152 | +runLayer("facade_ts", nxpp); |
0 commit comments