-
-
Notifications
You must be signed in to change notification settings - Fork 457
perf: rust backed PTC sampling #9263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from 2 commits
50045b8
91dfe02
749b637
9595cac
0fdd0bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| import {digest} from "@chainsafe/as-sha256"; | ||
| import { | ||
| computePtcIndices, | ||
| computePtcIndicesForEpoch, | ||
| computeProposerIndex as nativeComputeProposerIndex, | ||
| computeSyncCommitteeIndices as nativeComputeSyncCommitteeIndices, | ||
| } from "@chainsafe/swap-or-not-shuffle"; | ||
|
|
@@ -23,6 +25,7 @@ import {assert, bytesToBigInt, bytesToInt, intToBytes} from "@lodestar/utils"; | |
| import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js"; | ||
| import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js"; | ||
| import {computeEpochAtSlot, computeStartSlotAtEpoch} from "./epoch.js"; | ||
| import {EpochShuffling} from "./epochShuffling.js"; | ||
|
|
||
| /** | ||
| * Compute proposer indices for an epoch | ||
|
|
@@ -269,9 +272,12 @@ export function getNextSyncCommitteeIndices( | |
| } | ||
|
|
||
| /** | ||
| * Compute PTC for all slots in an epoch eagerly. | ||
| * Naive JS version of `computePayloadTimelinessCommitteesForEpoch`. | ||
| * Used to verify the optimized Rust-backed version. Not for production use. | ||
| * | ||
| * SLOW CODE - 🐢 | ||
| */ | ||
| export function computePayloadTimelinessCommitteesForEpoch( | ||
| export function naiveComputePayloadTimelinessCommitteesForEpoch( | ||
| state: BeaconStateAllForks, | ||
| epoch: number, | ||
| committees: Uint32Array[][], | ||
|
|
@@ -293,15 +299,60 @@ export function computePayloadTimelinessCommitteesForEpoch( | |
| slotSeedView.setUint32(epochSeed.length + 4, 0, true); | ||
| const slotSeed = digest(slotSeedInput); | ||
|
|
||
| result[i] = computePayloadTimelinessCommitteeForSlot(slotSeed, committees[i], effectiveBalanceIncrements); | ||
| result[i] = naiveComputePayloadTimelinessCommitteeForSlot(slotSeed, committees[i], effectiveBalanceIncrements); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Compute PTC for a single slot. | ||
| * Compute PTC for all slots in an epoch eagerly. | ||
| */ | ||
| export function computePayloadTimelinessCommitteeForSlot( | ||
| export function computePayloadTimelinessCommitteesForEpoch( | ||
| state: BeaconStateAllForks, | ||
| epoch: number, | ||
| epochShuffling: EpochShuffling, | ||
| effectiveBalanceIncrements: EffectiveBalanceIncrements | ||
| ): Uint32Array[] { | ||
| const epochSeed = getSeed(state, epoch, DOMAIN_PTC_ATTESTER); | ||
| const startSlot = epoch * SLOTS_PER_EPOCH; | ||
| const {committees, shuffling} = epochShuffling; | ||
|
|
||
| const slotOffsets = new Uint32Array(SLOTS_PER_EPOCH + 1); | ||
| for (let i = 0; i < SLOTS_PER_EPOCH; i++) { | ||
| let slotLen = 0; | ||
| for (const c of committees[i]) slotLen += c.length; | ||
| slotOffsets[i + 1] = slotOffsets[i] + slotLen; | ||
| } | ||
| if (shuffling.length === 0) { | ||
| throw Error("Validator indices must not be empty"); | ||
| } | ||
|
Comment on lines
+326
to
+328
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| const flat = computePtcIndicesForEpoch( | ||
| epochSeed, | ||
| startSlot, | ||
| SLOTS_PER_EPOCH, | ||
| shuffling, | ||
| slotOffsets, | ||
| effectiveBalanceIncrements, | ||
| PTC_SIZE, | ||
| MAX_EFFECTIVE_BALANCE_ELECTRA, | ||
| EFFECTIVE_BALANCE_INCREMENT | ||
| ); | ||
|
|
||
| const result = new Array<Uint32Array>(SLOTS_PER_EPOCH); | ||
| for (let i = 0; i < SLOTS_PER_EPOCH; i++) { | ||
| result[i] = flat.subarray(i * PTC_SIZE, (i + 1) * PTC_SIZE); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Naive JS version of `computePayloadTimelinessCommitteeForSlot`. | ||
| * Used to verify the optimized Rust-backed version. Not for production use. | ||
| * | ||
| * SLOW CODE - 🐢 | ||
| */ | ||
| export function naiveComputePayloadTimelinessCommitteeForSlot( | ||
| slotSeed: Uint8Array, | ||
| slotCommittees: Uint32Array[], | ||
| effectiveBalanceIncrements: EffectiveBalanceIncrements | ||
|
|
@@ -317,6 +368,31 @@ export function computePayloadTimelinessCommitteeForSlot( | |
| return computePayloadTimelinessCommitteeIndices(effectiveBalanceIncrements, allIndices, slotSeed); | ||
| } | ||
|
|
||
| /** | ||
| * Compute PTC for a single slot. | ||
| */ | ||
| export function computePayloadTimelinessCommitteeForSlot( | ||
| slotSeed: Uint8Array, | ||
| slotCommittees: Uint32Array[], | ||
| effectiveBalanceIncrements: EffectiveBalanceIncrements | ||
| ): Uint32Array { | ||
| const totalLen = slotCommittees.reduce((sum, c) => sum + c.length, 0); | ||
| const allIndices = new Uint32Array(totalLen); | ||
| let offset = 0; | ||
| for (const c of slotCommittees) { | ||
| allIndices.set(c, offset); | ||
| offset += c.length; | ||
| } | ||
| return computePtcIndices( | ||
| slotSeed, | ||
| allIndices, | ||
| effectiveBalanceIncrements, | ||
| PTC_SIZE, | ||
| MAX_EFFECTIVE_BALANCE_ELECTRA, | ||
| EFFECTIVE_BALANCE_INCREMENT | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Optimized version of PTC indices computation. | ||
| * Avoids BigInt conversions and uses DataView for efficient byte reading. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,15 @@ | ||
| import {bench, describe} from "@chainsafe/benchmark"; | ||
| import {ForkSeq} from "@lodestar/params"; | ||
| import {fromHex} from "@lodestar/utils"; | ||
| import {generatePerfTestCachedStateAltair} from "../../../src/testUtils/util.js"; | ||
| import {generatePerfTestCachedStateAltair, generatePerfTestCachedStateElectra} from "../../../src/testUtils/util.js"; | ||
| import { | ||
| computePayloadTimelinessCommitteeForSlot, | ||
| computePayloadTimelinessCommitteesForEpoch, | ||
| computeProposerIndex, | ||
| computeShuffledIndex, | ||
| getNextSyncCommitteeIndices, | ||
| naiveComputePayloadTimelinessCommitteeForSlot, | ||
| naiveComputePayloadTimelinessCommitteesForEpoch, | ||
| naiveComputeProposerIndex, | ||
| naiveGetNextSyncCommitteeIndices, | ||
| } from "../../../src/util/seed.js"; | ||
|
|
@@ -89,3 +93,89 @@ describe("computeShuffledIndex", () => { | |
| }); | ||
| } | ||
| }); | ||
|
|
||
| describe("computePayloadTimelinessCommitteeForSlot - pure TS vs Rust magic (250k-1M validators)", () => { | ||
| for (const vc of [250_000, 1_000_000]) { | ||
| const seed = new Uint8Array(32).fill(1); | ||
| const indices = new Uint32Array(Array.from({length: vc}, (_, i) => i)); | ||
| const effectiveBalanceIncrements = new Uint16Array(vc).fill(32); | ||
| const slotCommittees = [indices]; // single committee spanning all validators | ||
|
|
||
| bench({ | ||
| id: `naive TS - naiveComputePayloadTimelinessCommitteeForSlot - ${vc} validators`, | ||
| fn: () => { | ||
| naiveComputePayloadTimelinessCommitteeForSlot(seed, slotCommittees, effectiveBalanceIncrements); | ||
| }, | ||
| }); | ||
|
|
||
| bench({ | ||
| id: `(uses native Rust) -computePayloadTimelinessCommitteeForSlot - ${vc} validators`, | ||
| fn: () => { | ||
| computePayloadTimelinessCommitteeForSlot(seed, slotCommittees, effectiveBalanceIncrements); | ||
| }, | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| describe("computePayloadTimelinessCommitteesForEpoch - pure TS vs Rust magic (250k -1M validators)", () => { | ||
| for (const vc of [250_000, 1_000_000]) { | ||
| const cachedState = generatePerfTestCachedStateElectra({goBackOneSlot: false, vc}); | ||
| const {epochCtx} = cachedState; | ||
| const epoch = epochCtx.epoch; | ||
| const {effectiveBalanceIncrements} = epochCtx; | ||
|
|
||
| // eslint-disable-next-line no-console | ||
| console.log(`[vc=${vc}] effectiveBalanceIncrements[0]=${effectiveBalanceIncrements[0]}`); | ||
|
Comment on lines
+127
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| const naiveResult = naiveComputePayloadTimelinessCommitteesForEpoch( | ||
| cachedState, | ||
| epoch, | ||
| epochCtx.currentShuffling.committees, | ||
| effectiveBalanceIncrements | ||
| ); | ||
| const rustResult = computePayloadTimelinessCommitteesForEpoch( | ||
| cachedState, | ||
| epoch, | ||
| epochCtx.currentShuffling, | ||
| effectiveBalanceIncrements | ||
| ); | ||
| for (let i = 0; i < naiveResult.length; i++) { | ||
| const naive = naiveResult[i]; | ||
| const rust = rustResult[i]; | ||
| if (naive.length !== rust.length) { | ||
| throw new Error(`PTC length mismatch at slot ${i} (vc=${vc}): naive=${naive.length} rust=${rust.length}`); | ||
| } | ||
| for (let j = 0; j < naive.length; j++) { | ||
| if (naive[j] !== rust[j]) { | ||
| throw new Error( | ||
| `PTC index mismatch at slot ${i} position ${j} (vc=${vc}): naive=${naive[j]} rust=${rust[j]}` | ||
|
Comment on lines
+128
to
+151
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be removed, added this just to check the values from rust and native values |
||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| bench({ | ||
| id: `naive TS - naiveComputePayloadTimelinessCommitteesForEpoch - ${vc} validators`, | ||
| fn: () => { | ||
| naiveComputePayloadTimelinessCommitteesForEpoch( | ||
| cachedState, | ||
| epoch, | ||
| epochCtx.currentShuffling.committees, | ||
| effectiveBalanceIncrements | ||
| ); | ||
| }, | ||
| }); | ||
|
|
||
| bench({ | ||
| id: `(uses native Rust) -computePayloadTimelinessCommitteesForEpoch - ${vc} validators`, | ||
| fn: () => { | ||
| computePayloadTimelinessCommitteesForEpoch( | ||
| cachedState, | ||
| epoch, | ||
| epochCtx.currentShuffling, | ||
| effectiveBalanceIncrements | ||
| ); | ||
| }, | ||
| }); | ||
| } | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of a local tarball dependency (
file:../../...) is problematic for shared repositories as it breaks builds for other developers and CI environments that do not have the file at that specific relative path. Furthermore, thepnpm-lock.yamlchanges indicate that several supported platforms (e.g.,darwin-x64,linux-x64-gnu) have been removed and versions for native bindings have been downgraded to0.0.2. This dependency should be properly published to a registry or integrated via workspace references to maintain repository integrity and cross-platform support.