Skip to content

Commit

Permalink
feat: reuse arrays for getAll() api consumers
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Jul 15, 2024
1 parent 3bd71ca commit dfeecf5
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 12 deletions.
8 changes: 7 additions & 1 deletion packages/state-transition/src/block/processEth1Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CompositeViewDU<typeof ssz.phase0.Eth1Data>>();
/**
* Returns true if adding the given `eth1Data` to `state.eth1DataVotes` would
* result in a change to `state.eth1Data`.
Expand All @@ -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++;
Expand Down
19 changes: 15 additions & 4 deletions packages/state-transition/src/cache/epochTransitionCache.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -168,6 +168,13 @@ export interface EpochTransitionCache {
isActiveNextEpoch: boolean[];
}

/**
* This data is reused and never gc.
*/
const validators = new Array<phase0.Validator>();
const previousEpochParticipation = new Array<number>();
const currentEpochParticipation = new Array<number>();

export function beforeProcessEpoch(
state: CachedBeaconStateAllForks,
opts?: EpochTransitionCacheOpts
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
15 changes: 12 additions & 3 deletions packages/state-transition/src/epoch/getRewardsAndPenalties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +28,11 @@ type RewardPenaltyItem = {
timelyHeadReward: number;
};

/**
* This data is reused and never gc.
*/
const rewards = new Array<number>();
const penalties = new Array<number>();
/**
* An aggregate of getFlagIndexDeltas and getInactivityPenaltyDeltas that loop through process.statuses 1 time instead of 4.
*
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>();

/**
* Mutates `inactivityScores` from pre-calculated validator statuses.
*
Expand Down Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>();
/**
* Iterate over all validator and compute rewards and penalties to apply to balances.
*
Expand All @@ -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);
Expand Down
10 changes: 8 additions & 2 deletions packages/state-transition/src/util/balance.ts
Original file line number Diff line number Diff line change
@@ -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 "..";
Expand Down Expand Up @@ -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<phase0.Validator>();

/**
* 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,
Expand All @@ -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]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit dfeecf5

Please sign in to comment.