Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {bench, describe} from "@chainsafe/benchmark";
import {ContainerNodeStructType, ContainerType, ListCompositeType} from "@chainsafe/ssz";
import {PENDING_DEPOSITS_LIMIT} from "@lodestar/params";
import {ssz} from "@lodestar/types";

// PERF: Cost is O(pendingDeposits.length). In the worst case (large deposit queue after a big network event),
// this can be 50_000 items. Each item requires reading all 5 fields.
//
// Benchmarks ContainerType (current) vs ContainerNodeStructType (proposed) for field access performance.
// ContainerNodeStructType stores items as plain JS objects, avoiding tree traversal on every field read.

const NUM_DEPOSITS = 50_000;
const CHUNK = 100;

// Reuse the same field types as the existing PendingDeposit SSZ type
const fields = ssz.electra.PendingDeposit.fields;

const PendingDepositContainer = new ContainerType(fields, {typeName: "PendingDeposit", jsonCase: "eth2"});
const PendingDepositNodeStruct = new ContainerNodeStructType(fields, {typeName: "PendingDeposit", jsonCase: "eth2"});

const ListContainer = new ListCompositeType(PendingDepositContainer, PENDING_DEPOSITS_LIMIT);
const ListNodeStruct = new ListCompositeType(PendingDepositNodeStruct, PENDING_DEPOSITS_LIMIT);

function buildList(listType: typeof ListContainer): ReturnType<typeof ListContainer.defaultViewDU>;
function buildList(listType: typeof ListNodeStruct): ReturnType<typeof ListNodeStruct.defaultViewDU>;
function buildList(listType: typeof ListContainer | typeof ListNodeStruct) {
const view = listType.defaultViewDU();
const defaultDeposit = ssz.electra.PendingDeposit.defaultValue();
for (let i = 0; i < NUM_DEPOSITS; i++) {
if (listType === ListContainer) {
view.push(PendingDepositContainer.toViewDU(defaultDeposit));
} else {
view.push(PendingDepositNodeStruct.toViewDU(defaultDeposit));
}
}
view.commit();
return view;
Comment on lines +27 to +37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The buildList function can be simplified by hoisting the type check out of the loop. Instead of checking listType on every iteration, you can get the elementType from the list type once before the loop. This makes the code cleaner and slightly more performant.

  const view = listType.defaultViewDU();
  const defaultDeposit = ssz.electra.PendingDeposit.defaultValue();
  const itemType = listType.elementType;
  for (let i = 0; i < NUM_DEPOSITS; i++) {
    view.push(itemType.toViewDU(defaultDeposit));
  }
  view.commit();
  return view;

}

describe.skip(`processPendingDeposits - iterate ${NUM_DEPOSITS} deposits, access all fields`, () => {
const containerListView = buildList(ListContainer);
const nodeStructListView = buildList(ListNodeStruct);

bench({
id: `ContainerType - getReadonlyByRange chunk=${CHUNK}`,
yieldEventLoopAfterEach: true,
fn: () => {
let sum = 0;
for (let i = 0; i < NUM_DEPOSITS; i += CHUNK) {
const deposits = containerListView.getReadonlyByRange(i, CHUNK);
for (const deposit of deposits) {
sum += deposit.amount + deposit.slot;
void deposit.pubkey;
void deposit.withdrawalCredentials;
void deposit.signature;
}
}
if (sum === Number.MIN_SAFE_INTEGER) {
throw new Error("unreachable");
}
},
});

bench({
id: `ContainerNodeStructType - getReadonlyByRange chunk=${CHUNK}`,
yieldEventLoopAfterEach: true,
fn: () => {
let sum = 0;
for (let i = 0; i < NUM_DEPOSITS; i += CHUNK) {
const deposits = nodeStructListView.getReadonlyByRange(i, CHUNK);
for (const deposit of deposits) {
sum += deposit.amount + deposit.slot;
void deposit.pubkey;
void deposit.withdrawalCredentials;
void deposit.signature;
}
}
if (sum === Number.MIN_SAFE_INTEGER) {
throw new Error("unreachable");
}
},
});
});
7 changes: 4 additions & 3 deletions packages/types/src/electra/sszTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BitListType,
BitVectorType,
ContainerNodeStructType,
ContainerType,
ListBasicType,
ListCompositeType,
Expand Down Expand Up @@ -270,7 +271,7 @@ export const SignedBuilderBid = new ContainerType(
{typeName: "SignedBuilderBid", jsonCase: "eth2"}
);

export const PendingDeposit = new ContainerType(
export const PendingDeposit = new ContainerNodeStructType(
{
pubkey: BLSPubkey,
withdrawalCredentials: Bytes32,
Expand All @@ -285,7 +286,7 @@ export const PendingDeposit = new ContainerType(

export const PendingDeposits = new ListCompositeType(PendingDeposit, PENDING_DEPOSITS_LIMIT);

export const PendingPartialWithdrawal = new ContainerType(
export const PendingPartialWithdrawal = new ContainerNodeStructType(
{
validatorIndex: ValidatorIndex,
amount: Gwei,
Expand All @@ -299,7 +300,7 @@ export const PendingPartialWithdrawals = new ListCompositeType(
PENDING_PARTIAL_WITHDRAWALS_LIMIT
);

export const PendingConsolidation = new ContainerType(
export const PendingConsolidation = new ContainerNodeStructType(
{
sourceIndex: ValidatorIndex,
targetIndex: ValidatorIndex,
Expand Down
Loading