From 0d012d1eddcb9fdedf5855ba0b4d5b64c706a890 Mon Sep 17 00:00:00 2001 From: Jan Lewandowski Date: Fri, 6 Sep 2024 18:07:40 +0900 Subject: [PATCH] feat: benchmark for causallyRealted function --- package.json | 3 +- packages/object/src/hashgraph/index.ts | 50 +++++++++++-- .../object/tests/causallyrelated.bench.ts | 71 +++++++++++++++++++ 3 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 packages/object/tests/causallyrelated.bench.ts diff --git a/package.json b/package.json index a153b6de..f667bb59 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "docs": "typedoc", "proto-gen": "buf generate", "release": "release-it", - "test": "vitest" + "test": "vitest", + "bench": "vitest bench" }, "devDependencies": { "@biomejs/biome": "^1.8.3", diff --git a/packages/object/src/hashgraph/index.ts b/packages/object/src/hashgraph/index.ts index 940b3e56..cd3f8a39 100644 --- a/packages/object/src/hashgraph/index.ts +++ b/packages/object/src/hashgraph/index.ts @@ -1,7 +1,7 @@ import * as crypto from "node:crypto"; import { BitSet } from "./bitset.js"; -type Hash = string; +export type Hash = string; export type Operation = { type: string; value: T | null }; enum OperationType { @@ -191,7 +191,7 @@ export class HashGraph { while (j < order.length) { const moving = order[j]; - if (!this.areCausallyRelated(anchor, moving)) { + if (!this.areCausallyRelatedUsingBitsets(anchor, moving)) { const v1 = this.vertices.get(anchor); const v2 = this.vertices.get(moving); let action: ActionType; @@ -233,12 +233,11 @@ export class HashGraph { return result; } - // Time complexity: O(V), Space complexity: O(V) - areCausallyRelated(hash1: Hash, hash2: Hash): boolean { + // Amortised time complexity: O(1), Amortised space complexity: O(1) + areCausallyRelatedUsingBitsets(hash1: Hash, hash2: Hash): boolean { if (!this.arePredecessorsFresh) { this.topologicalSort(); } - const test1 = this.reachablePredecessors .get(hash1) @@ -250,6 +249,47 @@ export class HashGraph { return test1 || test2; } + // Time complexity: O(V), Space complexity: O(V) + areCausallyRelatedUsingBFS(hash1: Hash, hash2: Hash): boolean { + const visited = new Set(); + const stack = [hash1]; + + while (stack.length > 0) { + const current = stack.pop(); + if (current === hash2) return true; + if (current === undefined) continue; + visited.add(current); + + const vertex = this.vertices.get(current); + if (!vertex) continue; + for (const dep of vertex.dependencies) { + if (!visited.has(dep)) { + stack.push(dep); + } + } + } + + visited.clear(); + stack.push(hash2); + + while (stack.length > 0) { + const current = stack.pop(); + if (current === hash1) return true; + if (current === undefined) continue; + visited.add(current); + + const vertex = this.vertices.get(current); + if (!vertex) continue; + for (const dep of vertex.dependencies) { + if (!visited.has(dep)) { + stack.push(dep); + } + } + } + + return false; + } + // Time complexity: O(1), Space complexity: O(1) getFrontier(): Hash[] { return Array.from(this.frontier); diff --git a/packages/object/tests/causallyrelated.bench.ts b/packages/object/tests/causallyrelated.bench.ts new file mode 100644 index 00000000..1862eb57 --- /dev/null +++ b/packages/object/tests/causallyrelated.bench.ts @@ -0,0 +1,71 @@ +import test from "node:test"; +import { beforeEach, bench, describe } from "vitest"; +import { AddWinsSet } from "../../crdt/src/cros/AddWinsSet/index.js"; +import { + type Hash, + type TopologyObject, + merge, + newTopologyObject, +} from "../src/index.js"; + +describe("AreCausallyDependent benchmark", async () => { + const samples = 100000; + const tests: Hash[][] = []; + + const obj1 = await newTopologyObject("peer1", new AddWinsSet()); + const obj2 = await newTopologyObject("peer2", new AddWinsSet()); + const obj3 = await newTopologyObject("peer3", new AddWinsSet()); + + const cro1 = obj1.cro as AddWinsSet; + const cro2 = obj2.cro as AddWinsSet; + const cro3 = obj3.cro as AddWinsSet; + + cro1.add(1); + merge(obj2, obj1.hashGraph.getAllVertices()); + + cro1.add(1); + cro1.remove(2); + cro2.remove(2); + cro2.add(2); + + merge(obj3, obj1.hashGraph.getAllVertices()); + cro3.add(3); + cro1.remove(1); + + merge(obj1, obj2.hashGraph.getAllVertices()); + cro1.remove(3); + cro2.remove(1); + + merge(obj1, obj2.hashGraph.getAllVertices()); + merge(obj1, obj3.hashGraph.getAllVertices()); + + const vertices = obj1.hashGraph.getAllVertices(); + for (let i = 0; i < samples; i++) { + tests.push([ + vertices[Math.floor(Math.random() * vertices.length)].hash, + vertices[Math.floor(Math.random() * vertices.length)].hash, + ]); + } + + bench("Causality check using BFS", async () => { + const cro1 = obj1.cro as AddWinsSet; + + for (let i = 0; i < samples; i++) { + const result = obj1.hashGraph.areCausallyRelatedUsingBFS( + tests[i][0], + tests[i][1], + ); + } + }); + + bench("Causality check using Bitsets", async () => { + const cro1 = obj1.cro as AddWinsSet; + + for (let i = 0; i < samples; i++) { + const result = obj1.hashGraph.areCausallyRelatedUsingBitsets( + tests[i][0], + tests[i][1], + ); + } + }); +});