Skip to content

Commit

Permalink
feat: optimize linearization functions (#226)
Browse files Browse the repository at this point in the history
Co-authored-by: trungnotchung <[email protected]>
  • Loading branch information
hoangquocvietuet and trungnotchung authored Nov 23, 2024
1 parent 41eb7cf commit 5529f79
Show file tree
Hide file tree
Showing 7 changed files with 422 additions and 306 deletions.
21 changes: 21 additions & 0 deletions packages/object/src/hashgraph/bitset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,25 @@ export class BitSet {
.map((int) => int.toString(2).padStart(32, "0"))
.join("");
}

findNext(index: number, bit: number): number {
let wordIndex = Math.floor((index + 1) / 32);
const bitIndex = (index + 1) % 32;
let mask = ~((1 << bitIndex) - 1);

while (wordIndex < this.data.length) {
let currentWord = this.data[wordIndex];
if (bit === 0) currentWord = ~currentWord;
currentWord &= mask;

if (currentWord !== 0) {
return wordIndex * 32 + 31 - Math.clz32(currentWord & -currentWord);
}

wordIndex++;
mask = ~0;
}

return this.data.length * 32;
}
}
19 changes: 15 additions & 4 deletions packages/object/src/hashgraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,13 @@ export class HashGraph {
}

topologicalSort(updateBitsets = false): Hash[] {
this.reachablePredecessors.clear();
this.topoSortedIndex.clear();

const result = this.depthFirstSearch();
result.reverse();

if (!updateBitsets) return result;

this.reachablePredecessors.clear();
this.topoSortedIndex.clear();

// Double the size until it's enough to hold all the vertices
while (this.currentBitsetSize < result.length) this.currentBitsetSize *= 2;

Expand Down Expand Up @@ -316,6 +315,10 @@ export class HashGraph {
return true;
}

findNextCausallyUnrelated(hash: Hash, start: number): number | undefined {
return this.reachablePredecessors.get(hash)?.findNext(start, 0);
}

areCausallyRelatedUsingBFS(hash1: Hash, hash2: Hash): boolean {
return (
this._areCausallyRelatedUsingBFS(hash1, hash2) ||
Expand All @@ -338,6 +341,14 @@ export class HashGraph {
getAllVertices(): Vertex[] {
return Array.from(this.vertices.values());
}

getReachablePredecessors(hash: Hash): BitSet | undefined {
return this.reachablePredecessors.get(hash);
}

getCurrentBitsetSize(): number {
return this.currentBitsetSize;
}
}

function computeHash<T>(
Expand Down
65 changes: 50 additions & 15 deletions packages/object/src/linearize/multipleSemantics.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BitSet } from "../hashgraph/bitset.js";
import {
ActionType,
type Hash,
Expand All @@ -7,17 +8,25 @@ import {
} from "../hashgraph/index.js";

export function linearizeMultiple(hashGraph: HashGraph): Operation[] {
let order = hashGraph.topologicalSort(true);
const order = hashGraph.topologicalSort(true);
const dropped = new Array(order.length).fill(false);
const indices: Map<Hash, number> = new Map();
const result: Operation[] = [];
let i = 0;

while (i < order.length) {
if (dropped[i]) {
i++;
continue;
}
const anchor = order[i];
let j = i + 1;
let shouldIncrementI = true;

while (j < order.length) {
if (dropped[j]) {
j = hashGraph.findNextCausallyUnrelated(anchor, j) ?? order.length;
continue;
}
const moving = order[j];

if (!hashGraph.areCausallyRelatedUsingBitsets(anchor, moving)) {
Expand All @@ -26,8 +35,28 @@ export function linearizeMultiple(hashGraph: HashGraph): Operation[] {
indices.set(anchor, i);
concurrentOps.push(moving);
indices.set(moving, j);
let k = j + 1;
for (; k < order.length; k++) {

let reachableVertices: BitSet = new BitSet(
hashGraph.getCurrentBitsetSize(),
);
const anchorReachablePredecessors =
hashGraph.getReachablePredecessors(anchor);
if (anchorReachablePredecessors) {
reachableVertices = reachableVertices.or(anchorReachablePredecessors);
}
const movingReachablePredecessors =
hashGraph.getReachablePredecessors(moving);
if (movingReachablePredecessors) {
reachableVertices = reachableVertices.or(movingReachablePredecessors);
}

let k = reachableVertices.findNext(j, 0);
while (k < order.length) {
if (dropped[k]) {
k = reachableVertices.findNext(k, 0);
continue;
}

let add = true;
for (const hash of concurrentOps) {
if (hashGraph.areCausallyRelatedUsingBitsets(hash, order[k])) {
Expand All @@ -38,40 +67,46 @@ export function linearizeMultiple(hashGraph: HashGraph): Operation[] {
if (add) {
concurrentOps.push(order[k]);
indices.set(order[k], k);
const reachablePredecessors = hashGraph.getReachablePredecessors(
order[k],
);
if (reachablePredecessors) {
reachableVertices = reachableVertices.or(reachablePredecessors);
}
}

k = reachableVertices.findNext(k, 0);
}
const resolved = hashGraph.resolveConflicts(
concurrentOps.map((hash) => hashGraph.vertices.get(hash) as Vertex),
);

switch (resolved.action) {
case ActionType.Drop: {
const newOrder = [];
for (const hash of resolved.vertices || []) {
if (indices.get(hash) === i) shouldIncrementI = false;
order[indices.get(hash) || -1] = "";
dropped[indices.get(hash) || -1] = true;
}
for (const val of order) {
if (val !== "") newOrder.push(val);
if (dropped[i]) {
j = order.length;
}
order = newOrder;
if (!shouldIncrementI) j = order.length; // Break out of inner loop
break;
}
case ActionType.Nop:
j++;
j = hashGraph.findNextCausallyUnrelated(anchor, j) ?? order.length;
break;
default:
break;
}
} else {
j++;
j = hashGraph.findNextCausallyUnrelated(anchor, j) ?? order.length;
}
}

if (shouldIncrementI) {
if (!dropped[i]) {
const op = hashGraph.vertices.get(order[i])?.operation;
if (op && op.value !== null) result.push(op);
i++;
}
i++;
}

return result;
Expand Down
28 changes: 18 additions & 10 deletions packages/object/src/linearize/pairSemantics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ import {

export function linearizePair(hashGraph: HashGraph): Operation[] {
const order = hashGraph.topologicalSort(true);
const dropped = new Array(order.length).fill(false);
const result: Operation[] = [];
let i = 0;

while (i < order.length) {
if (dropped[i]) {
i++;
continue;
}
const anchor = order[i];
let j = i + 1;
let shouldIncrementI = true;

while (j < order.length) {
if (dropped[j]) {
j++;
continue;
}
const moving = order[j];

if (!hashGraph.areCausallyRelatedUsingBitsets(anchor, moving)) {
Expand All @@ -29,16 +37,16 @@ export function linearizePair(hashGraph: HashGraph): Operation[] {

switch (action) {
case ActionType.DropLeft:
order.splice(i, 1);
j = order.length; // Break out of inner loop
shouldIncrementI = false;
continue; // Continue outer loop without incrementing i
dropped[i] = true;
j = order.length;
break;
case ActionType.DropRight:
order.splice(j, 1);
continue; // Continue with the same j
dropped[j] = true;
j++;
break;
case ActionType.Swap:
[order[i], order[j]] = [order[j], order[i]];
j = order.length; // Break out of inner loop
j = i + 1;
break;
case ActionType.Nop:
j++;
Expand All @@ -49,11 +57,11 @@ export function linearizePair(hashGraph: HashGraph): Operation[] {
}
}

if (shouldIncrementI) {
if (!dropped[i]) {
const op = hashGraph.vertices.get(order[i])?.operation;
if (op && op.value !== null) result.push(op);
i++;
}
i++;
}

return result;
Expand Down
34 changes: 34 additions & 0 deletions packages/object/tests/bitset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,38 @@ describe("BitSet Test", () => {
other = other.and(bitset);
expect(other.get(0)).toBe(false);
});

test("find next index of one-bit", () => {
bitset.set(5, true);
bitset.set(10, true);
bitset.set(20, true);
bitset.set(30, true);
bitset.set(40, true);

expect(bitset.findNext(0, 1)).toBe(5);
expect(bitset.findNext(5, 1)).toBe(10);
expect(bitset.findNext(10, 1)).toBe(20);
expect(bitset.findNext(20, 1)).toBe(30);
expect(bitset.findNext(30, 1)).toBe(40);
expect(bitset.findNext(40, 1)).toBe(64);
});

test("find next index of zero-bit", () => {
for (let i = 0; i < 64; i++) {
bitset.set(i, true);
}

bitset.set(5, false);
bitset.set(10, false);
bitset.set(20, false);
bitset.set(30, false);
bitset.set(40, false);

expect(bitset.findNext(0, 0)).toBe(5);
expect(bitset.findNext(5, 0)).toBe(10);
expect(bitset.findNext(10, 0)).toBe(20);
expect(bitset.findNext(20, 0)).toBe(30);
expect(bitset.findNext(30, 0)).toBe(40);
expect(bitset.findNext(40, 0)).toBe(64);
});
});
Loading

0 comments on commit 5529f79

Please sign in to comment.