From dfeecf5c9831f4c281018941c28fe8968383b481 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Mon, 15 Jul 2024 14:01:26 +0700 Subject: [PATCH] feat: reuse arrays for getAll() api consumers --- .../src/block/processEth1Data.ts | 8 +++++++- .../src/cache/epochTransitionCache.ts | 19 +++++++++++++++---- .../src/epoch/getRewardsAndPenalties.ts | 15 ++++++++++++--- .../src/epoch/processInactivityUpdates.ts | 8 +++++++- .../src/epoch/processRewardsAndPenalties.ts | 7 ++++++- packages/state-transition/src/util/balance.ts | 10 ++++++++-- .../test/unit/cachedBeaconState.test.ts | 1 + 7 files changed, 56 insertions(+), 12 deletions(-) diff --git a/packages/state-transition/src/block/processEth1Data.ts b/packages/state-transition/src/block/processEth1Data.ts index 3d1927744328..2b6d8b62d79c 100644 --- a/packages/state-transition/src/block/processEth1Data.ts +++ b/packages/state-transition/src/block/processEth1Data.ts @@ -23,6 +23,10 @@ export function processEth1Data(state: CachedBeaconStateAllForks, eth1Data: phas state.eth1DataVotes.push(eth1DataView); } +/** + * This data is reused and never gc. + */ +const eth1DataVotes = new Array>(); /** * Returns true if adding the given `eth1Data` to `state.eth1DataVotes` would * result in a change to `state.eth1Data`. @@ -48,7 +52,9 @@ export function becomesNewEth1Data( // Then isEqualEth1DataView compares cached roots (HashObject as of Jan 2022) which is much cheaper // than doing structural equality, which requires tree -> value conversions let sameVotesCount = 0; - const eth1DataVotes = state.eth1DataVotes.getAllReadonly(); + // const eth1DataVotes = state.eth1DataVotes.getAllReadonly(); + eth1DataVotes.length = state.eth1DataVotes.length; + state.eth1DataVotes.getAllReadonly(eth1DataVotes); for (let i = 0; i < eth1DataVotes.length; i++) { if (isEqualEth1DataView(eth1DataVotes[i], newEth1Data)) { sameVotesCount++; diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index dc4edf26e084..47fcf3ce26c7 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -1,4 +1,4 @@ -import {Epoch, ValidatorIndex} from "@lodestar/types"; +import {Epoch, ValidatorIndex, phase0} from "@lodestar/types"; import {intDiv} from "@lodestar/utils"; import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MAX_EFFECTIVE_BALANCE} from "@lodestar/params"; @@ -168,6 +168,13 @@ export interface EpochTransitionCache { isActiveNextEpoch: boolean[]; } +/** + * This data is reused and never gc. + */ +const validators = new Array(); +const previousEpochParticipation = new Array(); +const currentEpochParticipation = new Array(); + export function beforeProcessEpoch( state: CachedBeaconStateAllForks, opts?: EpochTransitionCacheOpts @@ -197,7 +204,9 @@ export function beforeProcessEpoch( // To optimize memory each validator node in `state.validators` is represented with a special node type // `BranchNodeStruct` that represents the data as struct internally. This utility grabs the struct data directly // from the nodes without any extra transformation. The returned `validators` array contains native JS objects. - const validators = state.validators.getAllReadonlyValues(); + validators.length = state.validators.length; + state.validators.getAllReadonlyValues(validators); + const validatorCount = validators.length; // Clone before being mutated in processEffectiveBalanceUpdates @@ -329,7 +338,8 @@ export function beforeProcessEpoch( FLAG_CURR_HEAD_ATTESTER ); } else { - const previousEpochParticipation = (state as CachedBeaconStateAltair).previousEpochParticipation.getAll(); + previousEpochParticipation.length = (state as CachedBeaconStateAltair).previousEpochParticipation.length; + (state as CachedBeaconStateAltair).previousEpochParticipation.getAll(previousEpochParticipation); for (let i = 0; i < previousEpochParticipation.length; i++) { const status = statuses[i]; // this is required to pass random spec tests in altair @@ -339,7 +349,8 @@ export function beforeProcessEpoch( } } - const currentEpochParticipation = (state as CachedBeaconStateAltair).currentEpochParticipation.getAll(); + currentEpochParticipation.length = (state as CachedBeaconStateAltair).currentEpochParticipation.length; + (state as CachedBeaconStateAltair).currentEpochParticipation.getAll(currentEpochParticipation); for (let i = 0; i < currentEpochParticipation.length; i++) { const status = statuses[i]; // this is required to pass random spec tests in altair diff --git a/packages/state-transition/src/epoch/getRewardsAndPenalties.ts b/packages/state-transition/src/epoch/getRewardsAndPenalties.ts index dcffe986e0bb..20d25c0318ec 100644 --- a/packages/state-transition/src/epoch/getRewardsAndPenalties.ts +++ b/packages/state-transition/src/epoch/getRewardsAndPenalties.ts @@ -17,7 +17,7 @@ import { FLAG_PREV_TARGET_ATTESTER_UNSLASHED, hasMarkers, } from "../util/attesterStatus.js"; -import {isInInactivityLeak, newZeroedArray} from "../util/index.js"; +import {isInInactivityLeak} from "../util/index.js"; type RewardPenaltyItem = { baseReward: number; @@ -28,6 +28,11 @@ type RewardPenaltyItem = { timelyHeadReward: number; }; +/** + * This data is reused and never gc. + */ +const rewards = new Array(); +const penalties = new Array(); /** * An aggregate of getFlagIndexDeltas and getInactivityPenaltyDeltas that loop through process.statuses 1 time instead of 4. * @@ -48,8 +53,12 @@ export function getRewardsAndPenaltiesAltair( // TODO: Is there a cheaper way to measure length that going to `state.validators`? const validatorCount = state.validators.length; const activeIncrements = cache.totalActiveStakeByIncrement; - const rewards = newZeroedArray(validatorCount); - const penalties = newZeroedArray(validatorCount); + // const rewards = newZeroedArray(validatorCount); + // const penalties = newZeroedArray(validatorCount); + rewards.length = validatorCount; + rewards.fill(0); + penalties.length = validatorCount; + penalties.fill(0); const isInInactivityLeakBn = isInInactivityLeak(state); // effectiveBalance is multiple of EFFECTIVE_BALANCE_INCREMENT and less than MAX_EFFECTIVE_BALANCE diff --git a/packages/state-transition/src/epoch/processInactivityUpdates.ts b/packages/state-transition/src/epoch/processInactivityUpdates.ts index 5a84f9a48b66..40c72003c167 100644 --- a/packages/state-transition/src/epoch/processInactivityUpdates.ts +++ b/packages/state-transition/src/epoch/processInactivityUpdates.ts @@ -3,6 +3,11 @@ import {CachedBeaconStateAltair, EpochTransitionCache} from "../types.js"; import * as attesterStatusUtil from "../util/attesterStatus.js"; import {isInInactivityLeak} from "../util/index.js"; +/** + * This data is reused and never gc. + */ +const inactivityScoresArr = new Array(); + /** * Mutates `inactivityScores` from pre-calculated validator statuses. * @@ -30,7 +35,8 @@ export function processInactivityUpdates(state: CachedBeaconStateAltair, cache: // this avoids importing FLAG_ELIGIBLE_ATTESTER inside the for loop, check the compiled code const {FLAG_PREV_TARGET_ATTESTER_UNSLASHED, hasMarkers} = attesterStatusUtil; - const inactivityScoresArr = inactivityScores.getAll(); + inactivityScoresArr.length = state.validators.length; + inactivityScores.getAll(inactivityScoresArr); for (let j = 0; j < eligibleValidatorIndices.length; j++) { const i = eligibleValidatorIndices[j]; diff --git a/packages/state-transition/src/epoch/processRewardsAndPenalties.ts b/packages/state-transition/src/epoch/processRewardsAndPenalties.ts index 61680b81002a..e342c1f71e78 100644 --- a/packages/state-transition/src/epoch/processRewardsAndPenalties.ts +++ b/packages/state-transition/src/epoch/processRewardsAndPenalties.ts @@ -9,6 +9,10 @@ import { import {getAttestationDeltas} from "./getAttestationDeltas.js"; import {getRewardsAndPenaltiesAltair} from "./getRewardsAndPenalties.js"; +/** + * This data is reused and never gc. + */ +const balances = new Array(); /** * Iterate over all validator and compute rewards and penalties to apply to balances. * @@ -25,7 +29,8 @@ export function processRewardsAndPenalties( } const [rewards, penalties] = getRewardsAndPenalties(state, cache); - const balances = state.balances.getAll(); + balances.length = state.balances.length; + state.balances.getAll(balances); for (let i = 0, len = rewards.length; i < len; i++) { balances[i] += rewards[i] - penalties[i] - (slashingPenalties[i] ?? 0); diff --git a/packages/state-transition/src/util/balance.ts b/packages/state-transition/src/util/balance.ts index e305c745ab72..edff9acef134 100644 --- a/packages/state-transition/src/util/balance.ts +++ b/packages/state-transition/src/util/balance.ts @@ -1,5 +1,5 @@ import {EFFECTIVE_BALANCE_INCREMENT} from "@lodestar/params"; -import {Gwei, ValidatorIndex} from "@lodestar/types"; +import {Gwei, ValidatorIndex, phase0} from "@lodestar/types"; import {bigIntMax} from "@lodestar/utils"; import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js"; import {BeaconStateAllForks} from ".."; @@ -43,6 +43,11 @@ export function decreaseBalance(state: BeaconStateAllForks, index: ValidatorInde state.balances.set(index, Math.max(0, newBalance)); } +/** + * This data is reused and never gc. + */ +const validators = new Array(); + /** * This method is used to get justified balances from a justified state. * This is consumed by forkchoice which based on delta so we return "by increment" (in ether) value, @@ -63,7 +68,8 @@ export function getEffectiveBalanceIncrementsZeroInactive( validatorCount ); - const validators = justifiedState.validators.getAllReadonly(); + validators.length = validatorCount; + justifiedState.validators.getAllReadonlyValues(validators); let j = 0; for (let i = 0; i < validatorCount; i++) { if (i === activeIndices[j]) { diff --git a/packages/state-transition/test/unit/cachedBeaconState.test.ts b/packages/state-transition/test/unit/cachedBeaconState.test.ts index 2891cd3e6216..f1a204ee2de4 100644 --- a/packages/state-transition/test/unit/cachedBeaconState.test.ts +++ b/packages/state-transition/test/unit/cachedBeaconState.test.ts @@ -84,6 +84,7 @@ describe("CachedBeaconState", () => { state.validators.get(i).effectiveBalance += 1; } } + state.commit(); if (validatorCountDelta < 0) { state.validators = state.validators.sliceTo(state.validators.length - 1 + validatorCountDelta);