From 21ae39e8d72b09d64c0fc073acb42be3121892f3 Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:21:50 +0200 Subject: [PATCH] Add capella state transition perf tests --- .../test/perf/epoch/epochCapella.test.ts | 159 ++++++++++++++++++ packages/state-transition/test/perf/params.ts | 5 + .../test/utils/testFileCache.ts | 5 +- 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 packages/state-transition/test/perf/epoch/epochCapella.test.ts diff --git a/packages/state-transition/test/perf/epoch/epochCapella.test.ts b/packages/state-transition/test/perf/epoch/epochCapella.test.ts new file mode 100644 index 000000000000..86620fa2dfcf --- /dev/null +++ b/packages/state-transition/test/perf/epoch/epochCapella.test.ts @@ -0,0 +1,159 @@ +import {itBench, setBenchOpts} from "@dapplion/benchmark"; +import {ForkSeq} from "@lodestar/params"; +import { + computeStartSlotAtEpoch, + CachedBeaconStateAllForks, + CachedBeaconStateCapella, + CachedBeaconStateAltair, + beforeProcessEpoch, +} from "../../../src/index.js"; +import {getNetworkCachedState, beforeValue, LazyValue} from "../../utils/index.js"; +import {StateEpoch} from "../types.js"; +import {capellaState} from "../params.js"; +import {processJustificationAndFinalization} from "../../../src/epoch/processJustificationAndFinalization.js"; +import {processInactivityUpdates} from "../../../src/epoch/processInactivityUpdates.js"; +import {processRewardsAndPenalties} from "../../../src/epoch/processRewardsAndPenalties.js"; +import {processRegistryUpdates} from "../../../src/epoch/processRegistryUpdates.js"; +import {processSlashings} from "../../../src/epoch/processSlashings.js"; +import {processEth1DataReset} from "../../../src/epoch/processEth1DataReset.js"; +import {processEffectiveBalanceUpdates} from "../../../src/epoch/processEffectiveBalanceUpdates.js"; +import {processSlashingsReset} from "../../../src/epoch/processSlashingsReset.js"; +import {processRandaoMixesReset} from "../../../src/epoch/processRandaoMixesReset.js"; +import {processHistoricalRootsUpdate} from "../../../src/epoch/processHistoricalRootsUpdate.js"; +import {processParticipationFlagUpdates} from "../../../src/epoch/processParticipationFlagUpdates.js"; +import {processEpoch} from "../../../src/epoch/index.js"; + +const slot = computeStartSlotAtEpoch(capellaState.epoch) - 1; +const stateId = `${capellaState.network}_e${capellaState.epoch}`; +const fork = ForkSeq.altair; + +describe(`capella processEpoch - ${stateId}`, () => { + setBenchOpts({ + yieldEventLoopAfterEach: true, // So SubTree(s)'s WeakRef can be garbage collected https://github.com/nodejs/node/issues/39902 + }); + + const stateOg = beforeValue(async () => { + const state = await getNetworkCachedState(capellaState.network, slot, 300_000); + state.hashTreeRoot(); + return state; + }, 300_000); + + itBench({ + id: `capella processEpoch - ${stateId}`, + yieldEventLoopAfterEach: true, // So SubTree(s)'s WeakRef can be garbage collected https://github.com/nodejs/node/issues/39902 + beforeEach: () => stateOg.value.clone(), + fn: (state) => { + const cache = beforeProcessEpoch(state); + processEpoch(fork, state as CachedBeaconStateCapella, cache); + state.epochCtx.afterProcessEpoch(state, cache); + // Simulate root computation through the next block to account for changes + // 74184 hash64 ops - 92.730 ms + state.hashTreeRoot(); + }, + }); + + // Only in local environment compute a full breakdown of the cost of each step + describe(`capella processEpoch steps - ${stateId}`, () => { + setBenchOpts({noThreshold: true}); + + benchmarkAltairEpochSteps(stateOg, stateId); + }); +}); + +function benchmarkAltairEpochSteps(stateOg: LazyValue, stateId: string): void { + const cache = beforeValue(() => beforeProcessEpoch(stateOg.value)); + + // const getPerfState = (): CachedBeaconStateCapella => { + // const state = originalState.clone(); + // state.setStateCachesAsTransient(); + // return state; + // }; + + itBench({ + id: `${stateId} - capella beforeProcessEpoch`, + fn: () => { + beforeProcessEpoch(stateOg.value); + }, + }); + + itBench({ + id: `${stateId} - capella processJustificationAndFinalization`, + beforeEach: () => stateOg.value.clone(), + fn: (state) => processJustificationAndFinalization(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processInactivityUpdates`, + beforeEach: () => stateOg.value.clone() as CachedBeaconStateAltair, + fn: (state) => processInactivityUpdates(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processRewardsAndPenalties`, + beforeEach: () => stateOg.value.clone() as CachedBeaconStateCapella, + fn: (state) => processRewardsAndPenalties(state, cache.value), + }); + + // TODO: Needs a better state to test with, current does not include enough actions: 17.715 us/op + itBench({ + id: `${stateId} - capella processRegistryUpdates`, + beforeEach: () => stateOg.value.clone(), + fn: (state) => processRegistryUpdates(state, cache.value), + }); + + // TODO: Needs a better state to test with, current does not include enough actions: 39.985 us/op + itBench({ + id: `${stateId} - capella processSlashings`, + beforeEach: () => stateOg.value.clone() as CachedBeaconStateCapella, + fn: (state) => processSlashings(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processEth1DataReset`, + beforeEach: () => stateOg.value.clone(), + fn: (state) => processEth1DataReset(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processEffectiveBalanceUpdates`, + beforeEach: () => stateOg.value.clone(), + fn: (state) => processEffectiveBalanceUpdates(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processSlashingsReset`, + beforeEach: () => stateOg.value.clone(), + fn: (state) => processSlashingsReset(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processRandaoMixesReset`, + beforeEach: () => stateOg.value.clone(), + fn: (state) => processRandaoMixesReset(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processHistoricalRootsUpdate`, + beforeEach: () => stateOg.value.clone(), + fn: (state) => processHistoricalRootsUpdate(state, cache.value), + }); + + itBench({ + id: `${stateId} - capella processParticipationFlagUpdates`, + beforeEach: () => stateOg.value.clone() as CachedBeaconStateAltair, + fn: (state) => processParticipationFlagUpdates(state), + }); + + itBench({ + id: `${stateId} - capella afterProcessEpoch`, + // Compute a state and cache after running processEpoch() since those values are mutated + before: () => { + const state = stateOg.value.clone(); + const cacheAfter = beforeProcessEpoch(state); + processEpoch(fork, state, cacheAfter); + return {state, cache: cacheAfter}; + }, + beforeEach: ({state, cache}) => ({state: state.clone(), cache}), + fn: ({state, cache}) => state.epochCtx.afterProcessEpoch(state, cache), + }); +} diff --git a/packages/state-transition/test/perf/params.ts b/packages/state-transition/test/perf/params.ts index 9ab27c62907c..4b6ea2dff90a 100644 --- a/packages/state-transition/test/perf/params.ts +++ b/packages/state-transition/test/perf/params.ts @@ -11,6 +11,11 @@ export const altairState = { epoch: 81889, // Post altair fork }; +export const capellaState = { + network: "mainnet" as const, + epoch: 217614, // Post capella fork +}; + export const rangeSyncTest = { network: "mainnet" as const, startSlot: 3766816, // Post altair, first slot in epoch 117713 diff --git a/packages/state-transition/test/utils/testFileCache.ts b/packages/state-transition/test/utils/testFileCache.ts index cba010bd3738..e752f3c36e68 100644 --- a/packages/state-transition/test/utils/testFileCache.ts +++ b/packages/state-transition/test/utils/testFileCache.ts @@ -104,7 +104,10 @@ async function downloadTestFile(fileId: string): Promise { // eslint-disable-next-line no-console console.log(`Downloading file ${fileUrl}`); - const res = await got(fileUrl, {responseType: "buffer"}); + const res = await got(fileUrl, {responseType: "buffer"}).catch((e: Error) => { + e.message = `Error downloading ${fileUrl}: ${e.message}`; + throw e; + }); return res.body; }