Skip to content

Commit

Permalink
chore: add benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Aug 9, 2024
1 parent a6191d4 commit 6aaa7bd
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 3 deletions.
43 changes: 40 additions & 3 deletions packages/persistent-merkle-tree/test/perf/hasher.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {itBench} from "@dapplion/benchmark";
import {HashObject, uint8ArrayToHashObject} from "../../src/hasher";
import {HashObject, setHasher, uint8ArrayToHashObject} from "../../src/hasher";
import {hasher as asShaHasher} from "../../src/hasher/as-sha256";
import {hasher as nobleHasher} from "../../src/hasher/noble";
import {hasher as hashtreeHasher} from "../../src/hasher/hashtree";
import {buildComparisonTrees} from "../utils/tree";
import { HashComputationLevel, getHashComputations } from "../../src";
import {HashComputationLevel, getHashComputations} from "../../src";

describe("hasher", function () {
this.timeout(0);
Expand Down Expand Up @@ -65,4 +65,41 @@ describe("hasher", function () {
}
});

// TODO - batch: test more methods
describe.only("hashtree", function () {
itBench({
id: `getHashComputations`,
beforeEach: () => {
const [tree] = buildComparisonTrees(16);
return tree;
},
fn: (tree) => {
const hcByLevel: HashComputationLevel[] = [];
getHashComputations(tree, 0, hcByLevel);
},
});

itBench({
id: `executeHashComputations - hashtree`,
beforeEach: () => {
const [tree] = buildComparisonTrees(16);
return tree;
},
fn: (tree) => {
const hcByLevel: HashComputationLevel[] = [];
getHashComputations(tree, 0, hcByLevel);
hashtreeHasher.executeHashComputations(hcByLevel);
},
});

itBench({
id: `root - hashtree`,
beforeEach: () => {
const [tree] = buildComparisonTrees(16);
setHasher(hashtreeHasher);
return tree;
},
fn: (tree) => {
tree.root;
},
});
});
214 changes: 214 additions & 0 deletions packages/ssz/test/perf/eth2/beaconState.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import {itBench, setBenchOpts} from "@dapplion/benchmark";
import {HashComputationLevel, executeHashComputations, HashComputationGroup} from "@chainsafe/persistent-merkle-tree";
import {BeaconState} from "../../lodestarTypes/altair/sszTypes";
import {BitArray, CompositeViewDU, toHexString} from "../../../src";
import {preset} from "../../lodestarTypes/params";
const {SLOTS_PER_HISTORICAL_ROOT, EPOCHS_PER_ETH1_VOTING_PERIOD, SLOTS_PER_EPOCH} = preset;

const vc = 200_000;
const numModified = vc / 20;
// every we increase vc, need to change this value from "recursive hash" test
const expectedRoot = "0x759d635af161ac1e4f4af11aa7721fd4996253af50f8a81e5003bbb4cbcaae42";

/**
* This simulates a BeaconState being modified after an epoch transition in lodestar
* The fresh tree batch hash bechmark is in packages/persistent-merkle-tree/test/perf/node.test.ts
* Note that this benchmark is not very stable because we cannot apply runsFactor as once commit() we
* cannot compute HashComputationGroup again.
* Increasing number of validators could be OOM since we have to create BeaconState every time
*/
describe(`BeaconState ViewDU partially modified tree vc=${vc} numModified=${numModified}`, function () {
setBenchOpts({
minMs: 20_000,
});

itBench({
id: `BeaconState ViewDU hashTreeRoot() vc=${vc}`,
beforeEach: () => createPartiallyModifiedDenebState(),
fn: (state: CompositeViewDU<typeof BeaconState>) => {
state.hashTreeRoot();
if (toHexString(state.node.root) !== expectedRoot) {
throw new Error("hashTreeRoot does not match expectedRoot");
}
},
});

itBench({
id: `BeaconState ViewDU recursive hash - commit step vc=${vc}`,
beforeEach: () => createPartiallyModifiedDenebState(),
fn: (state: CompositeViewDU<typeof BeaconState>) => {
state.commit();
},
});

itBench({
id: `BeaconState ViewDU validator tree creation vc=${numModified}`,
beforeEach: () => {
const state = createPartiallyModifiedDenebState();
state.commit();
return state;
},
fn: (state: CompositeViewDU<typeof BeaconState>) => {
const validators = state.validators;
for (let i = 0; i < numModified; i++) {
validators.getReadonly(i).node.left;
}
},
});

const hc = new HashComputationGroup();
itBench({
id: `BeaconState ViewDU batchHashTreeRoot vc=${vc}`,
beforeEach: () => createPartiallyModifiedDenebState(),
fn: (state: CompositeViewDU<typeof BeaconState>) => {
// commit() step is inside hashTreeRoot(), reuse HashComputationGroup
if (toHexString(state.batchHashTreeRoot(hc)) !== expectedRoot) {
throw new Error("batchHashTreeRoot does not match expectedRoot");
}
state.batchHashTreeRoot(hc);
},
});

itBench({
id: `BeaconState ViewDU hashTreeRoot - commit step vc=${vc}`,
beforeEach: () => createPartiallyModifiedDenebState(),
fn: (state: CompositeViewDU<typeof BeaconState>) => {
state.commit(0, []);
},
});

itBench({
id: `BeaconState ViewDU hashTreeRoot - hash step vc=${vc}`,
beforeEach: () => {
const state = createPartiallyModifiedDenebState();
const hcByLevel: HashComputationLevel[] = [];
state.commit(0, hcByLevel);
return hcByLevel;
},
fn: (hcByLevel) => {
executeHashComputations(hcByLevel);
},
});
});

let originalState: CompositeViewDU<typeof BeaconState> | null = null;
function createPartiallyModifiedDenebState(): CompositeViewDU<typeof BeaconState> {
if (originalState === null) {
originalState = createDenebState(vc);
// cache all roots
// the original state is huge, do not call hashTreeRoot() here
originalState.commit();
originalState.node.root;
}

const state = originalState.clone();
state.slot++;
state.latestBlockHeader = BeaconState.fields.latestBlockHeader.toViewDU({
slot: 1000,
proposerIndex: 1,
parentRoot: Buffer.alloc(32, 0xac),
stateRoot: Buffer.alloc(32, 0xed),
bodyRoot: Buffer.alloc(32, 0x32),
});
state.blockRoots.set(0, Buffer.alloc(32, 0x01));
state.stateRoots.set(0, Buffer.alloc(32, 0x01));
state.historicalRoots.set(0, Buffer.alloc(32, 0x01));
for (let i = 0; i < numModified; i++) {
state.validators.get(i).effectiveBalance += 1e9;
}
state.balances = BeaconState.fields.balances.toViewDU(Array.from({length: vc}, () => 32e9));

state.eth1Data = BeaconState.fields.eth1Data.toViewDU({
depositRoot: Buffer.alloc(32, 0x02),
depositCount: 1000,
blockHash: Buffer.alloc(32, 0x03),
});
state.eth1DataVotes.set(0, state.eth1Data);
state.eth1DepositIndex++;
state.randaoMixes.set(0, Buffer.alloc(32, 0x02));
state.slashings.set(0, BigInt(1e9));

state.justificationBits = BeaconState.fields.justificationBits.toViewDU(
BitArray.fromBoolArray([true, false, true, true])
);
state.previousJustifiedCheckpoint = BeaconState.fields.previousJustifiedCheckpoint.toViewDU({
epoch: 1000,
root: Buffer.alloc(32, 0x01),
});
state.currentJustifiedCheckpoint = BeaconState.fields.currentJustifiedCheckpoint.toViewDU({
epoch: 1000,
root: Buffer.alloc(32, 0x01),
});
state.finalizedCheckpoint = BeaconState.fields.finalizedCheckpoint.toViewDU({
epoch: 1000,
root: Buffer.alloc(32, 0x01),
});
return state;
}

function createDenebState(vc: number): CompositeViewDU<typeof BeaconState> {
const state = BeaconState.defaultViewDU();
state.genesisTime = 1e9;
state.genesisValidatorsRoot = Buffer.alloc(32, 1);
state.slot = 1_000_000;
state.fork = BeaconState.fields.fork.toViewDU({
epoch: 1000,
previousVersion: Buffer.alloc(4, 0x03),
currentVersion: Buffer.alloc(4, 0x04),
});
state.latestBlockHeader = BeaconState.fields.latestBlockHeader.toViewDU({
slot: 1000,
proposerIndex: 1,
parentRoot: Buffer.alloc(32, 0xac),
stateRoot: Buffer.alloc(32, 0xed),
bodyRoot: Buffer.alloc(32, 0x32),
});
state.blockRoots = BeaconState.fields.blockRoots.toViewDU(
Array.from({length: 1_000_000}, () => Buffer.alloc(32, 0x01))
);
state.stateRoots = BeaconState.fields.stateRoots.toViewDU(
Array.from({length: 1_000_000}, () => Buffer.alloc(32, 0x01))
);
state.historicalRoots = BeaconState.fields.historicalRoots.toViewDU(
Array.from({length: 1_000_000}, () => Buffer.alloc(32, 0x01))
);
state.eth1DataVotes = BeaconState.fields.eth1DataVotes.toViewDU(
Array.from({length: EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH}, () => {
return {
depositRoot: Buffer.alloc(32, 0x04),
depositCount: 1000,
blockHash: Buffer.alloc(32, 0x05),
};
})
);
state.eth1DepositIndex = 1000;
const validators = Array.from({length: vc}, () => {
return {
pubkey: Buffer.alloc(48, 0xaa),
withdrawalCredentials: Buffer.alloc(32, 0xbb),
effectiveBalance: 32e9,
slashed: false,
activationEligibilityEpoch: 1_000_000,
activationEpoch: 2_000_000,
exitEpoch: 3_000_000,
withdrawableEpoch: 4_000_000,
};
});
state.validators = BeaconState.fields.validators.toViewDU(validators);
state.balances = BeaconState.fields.balances.toViewDU(Array.from({length: vc}, () => 32e9));
// randomMixes
state.randaoMixes = BeaconState.fields.randaoMixes.toViewDU(
Array.from({length: SLOTS_PER_HISTORICAL_ROOT}, () => Buffer.alloc(32, 0x01))
);
// slashings
state.slashings = BeaconState.fields.slashings.toViewDU(
Array.from({length: SLOTS_PER_HISTORICAL_ROOT}, () => BigInt(1e9))
);
state.previousEpochParticipation = BeaconState.fields.previousEpochParticipation.toViewDU(
Array.from({length: vc}, () => 7)
);
state.currentEpochParticipation = BeaconState.fields.previousEpochParticipation.toViewDU(
Array.from({length: vc}, () => 7)
);
return state;
}

0 comments on commit 6aaa7bd

Please sign in to comment.