Skip to content

Commit

Permalink
Add more benchmarks (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
dapplion authored Jan 18, 2022
1 parent 4245cb6 commit e7c4905
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 48 deletions.
56 changes: 55 additions & 1 deletion packages/ssz/test/perf/deserializationEth2.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import {itBench} from "@dapplion/benchmark";
import * as sszPhase0 from "../lodestarTypes/phase0/sszTypes";
import * as sszAltair from "../lodestarTypes/altair/sszTypes";
import {getAttestation, getSignedAggregateAndProof, getSignedContributionAndProof} from "../utils/generateEth2Objs";
import {BeaconState} from "../lodestarTypes/altair/types";
import {
getAttestation,
getOnce,
getRandomState,
getSignedAggregateAndProof,
getSignedContributionAndProof,
} from "../utils/generateEth2Objs";
import {CompositeType, TreeBacked, Type} from "../../src";

describe("Deserialization of frequent eth2 objects", () => {
itBench<Uint8Array, Uint8Array>({
Expand Down Expand Up @@ -48,4 +56,50 @@ describe("Deserialization of frequent eth2 objects", () => {
sszAltair.SignedContributionAndProof.deserialize(bytes);
},
});

for (const validatorCount of [300_000]) {
// Compute once for all benchmarks only if run
const getStateVc = getOnce(() => getRandomState(validatorCount));
const getStateTreeBacked = getOnce(() => sszAltair.BeaconState.createTreeBackedFromStruct(getStateVc()));

itBench<Uint8Array, Uint8Array>({
id: `BeaconState vc ${validatorCount} - deserialize tree`,
before: () => getStateTreeBacked().serialize(),
beforeEach: (bytes) => bytes,
fn: (bytes) => {
sszAltair.BeaconState.createTreeBackedFromBytes(bytes);
},
});

itBench({
id: `BeaconState vc ${validatorCount} - serialize tree`,
fn: () => {
getStateTreeBacked().serialize();
},
});

for (const [fieldName, fieldType] of Object.entries(sszAltair.BeaconState.fields)) {
// Only benchmark big data structures
if (fieldType.getMaxSerializedLength() < 10e6) {
continue;
}

itBench<Uint8Array, Uint8Array>({
id: `BeaconState.${fieldName} vc ${validatorCount} - deserialize tree`,
before: () =>
(getStateTreeBacked()[fieldName as keyof BeaconState] as unknown as TreeBacked<Type<unknown>>).serialize(),
beforeEach: (bytes) => bytes,
fn: (bytes) => {
(fieldType as CompositeType<any>).createTreeBackedFromBytes(bytes);
},
});

itBench<BeaconState, BeaconState>({
id: `BeaconState.${fieldName} vc ${validatorCount} - serialize tree`,
fn: () => {
(getStateTreeBacked()[fieldName as keyof BeaconState] as unknown as TreeBacked<Type<unknown>>).serialize();
},
});
}
}
});
148 changes: 103 additions & 45 deletions packages/ssz/test/perf/serdes.test.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,130 @@
import {itBench} from "@dapplion/benchmark";
import {BigIntUintType, BitListType, CompositeType, CompositeValue, ContainerType, ListType} from "../../src";
import {
BigIntUintType,
NumberUintType,
Number64UintType,
BitListType,
CompositeType,
ContainerType,
BasicListType,
CompositeListType,
CompositeVectorType,
ByteVectorType,
ContainerLeafNodeStructType,
} from "../../src";
import {Validator} from "../lodestarTypes/phase0/sszTypes";

describe("SSZ (de)serialize", () => {
const Gwei = new BigIntUintType({byteLength: 8});

type TestCase<T extends CompositeValue> = {
id: string;
type: CompositeType<T>;
getValue?: () => T;
};

function getBigListTestCase(arrLen: number): TestCase<bigint[]> {
return {
id: `List(BigInt) ${arrLen}`,
type: new ListType({elementType: Gwei, limit: arrLen}),
getValue: () => Array.from({length: arrLen}, () => BigInt(31217089836)),
};
const uint8 = new NumberUintType({byteLength: 1});
const uint64Number = new Number64UintType();
const Uint64Bigint = new BigIntUintType({byteLength: 8});
const Root = new ByteVectorType({length: 32});

runTestCase("Simple object", new ContainerType({fields: {a: Uint64Bigint, b: Uint64Bigint, c: Uint64Bigint}}));
runTestCase("aggregationBits", new BitListType({limit: 2048}), () => fillArray(128, true));

for (const arrLen of [100_000]) {
runTestCase(`List(uint8) ${arrLen}`, new BasicListType({elementType: uint8, limit: arrLen}), () =>
fillArray(arrLen, 7)
);

runTestCase(`List(uint64Number) ${arrLen}`, new BasicListType({elementType: uint64Number, limit: arrLen}), () =>
fillArray(arrLen, 31217089836)
);

runTestCase(`List(Uint64Bigint) ${arrLen}`, new BasicListType({elementType: Uint64Bigint, limit: arrLen}), () =>
fillArray(arrLen, BigInt(31217089836))
);

runTestCase(`Vector(Root) ${arrLen}`, new CompositeVectorType({elementType: Root, length: arrLen}), () =>
fillArray(arrLen, uint8ArrayFill(32, 0xaa))
);

const ValidatorContainer = new ContainerType({fields: Validator.fields});
runTestCase(
`List(Validator) ${arrLen}`,
new CompositeListType({elementType: ValidatorContainer, limit: arrLen}),
() =>
fillArray(arrLen, {
pubkey: uint8ArrayFill(48, 0xaa),
withdrawalCredentials: uint8ArrayFill(32, 0xaa),
effectiveBalance: 32e9,
slashed: false,
activationEligibilityEpoch: 1e6,
activationEpoch: 2e6,
exitEpoch: 3e6,
withdrawableEpoch: 4e6,
})
);

const ValidatorNodeStruct = new ContainerLeafNodeStructType({fields: Validator.fields});
runTestCase(
`List(Validator-NS) ${arrLen}`,
new CompositeListType({elementType: ValidatorNodeStruct, limit: arrLen}),
() =>
fillArray(arrLen, {
pubkey: uint8ArrayFill(48, 0xaa),
withdrawalCredentials: uint8ArrayFill(32, 0xaa),
effectiveBalance: 32e9,
slashed: false,
activationEligibilityEpoch: 1e6,
activationEpoch: 2e6,
exitEpoch: 3e6,
withdrawableEpoch: 4e6,
})
);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const testCases: TestCase<any>[] = [
{
id: "Simple object",
type: new ContainerType({
fields: {a: Gwei, b: Gwei, c: Gwei},
}),
},
{
id: "aggregationBits",
type: new BitListType({limit: 2048}),
getValue: () => Array.from({length: 128}, () => true),
},

getBigListTestCase(1e3),
getBigListTestCase(1e4),
getBigListTestCase(1e5),
];

for (const {id, type, getValue} of testCases) {
function runTestCase<T extends CompositeType<any>>(
id: string,
type: T,
getValue?: () => T extends CompositeType<infer V> ? V : never
): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const struct = getValue ? getValue() : type.defaultValue();
const binary = type.serialize(struct);
const bytes = type.serialize(struct);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const treeBacked = type.createTreeBackedFromStruct(struct);
const view = type.struct_convertToTree(struct);

itBench(`${id} binary -> struct`, () => {
type.struct_deserialize(binary);
type.deserialize(bytes);
});

itBench(`${id} binary -> tree_backed`, () => {
type.tree_deserialize(binary);
type.createTreeBackedFromBytes(bytes);
});

itBench(`${id} struct -> tree_backed`, () => {
// Don't track struct <-> tree_backed conversions since they are not required to be fast
itBench({id: `${id} struct -> tree_backed`, noThreshold: true}, () => {
type.struct_convertToTree(struct);
});

itBench(`${id} tree_backed -> struct`, () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
type.tree_convertToStruct(treeBacked.tree);
itBench({id: `${id} tree_backed -> struct`, noThreshold: true}, () => {
type.createTreeBackedFromStruct(struct);
});

itBench(`${id} struct -> binary`, () => {
type.serialize(struct);
});

itBench(`${id} tree_backed -> binary`, () => {
type.serialize(treeBacked);
const output = new Uint8Array(type.tree_getSerializedLength(view));
return type.tree_serializeToBytes(view, output, 0);
});
}
});

function fillArray<T>(len: number, value: T): T[] {
const values: T[] = [];
for (let i = 0; i < len; i++) {
values.push(value);
}
return values;
}

function uint8ArrayFill(byteLen: number, fill: number): Uint8Array {
const uint8Array = new Uint8Array(byteLen);
for (let i = 0; i < byteLen; i++) {
uint8Array[i] = fill;
}
return uint8Array;
}
2 changes: 1 addition & 1 deletion packages/ssz/test/perf/validators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe("Validator vs ValidatorLeafNodeStruct", () => {
serialize: (tb) => tb.serialize(),
set_exitEpoch_and_hashTreeRoot: (tb) => {
tb.exitEpoch = 6435;
tb.createProof([["exitEpoch"]]);
tb.hashTreeRoot();
},
};

Expand Down
34 changes: 33 additions & 1 deletion packages/ssz/test/utils/generateEth2Objs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
import * as sszAltair from "../lodestarTypes/altair/sszTypes";
import {Attestation, SignedAggregateAndProof, SignedBeaconBlock} from "../lodestarTypes/phase0/types";
import {SignedContributionAndProof} from "../lodestarTypes/altair/types";
import {SignedContributionAndProof, BeaconState} from "../lodestarTypes/altair/types";
import {BitList} from "../../src";

// Typical mainnet numbers
const BITS_PER_ATTESTATION = 90;
const ATTESTATIONS_PER_BLOCK = 90;

export function getRandomState(validatorCount: number): BeaconState {
const state = sszAltair.BeaconState.defaultValue();
for (let i = 0; i < validatorCount; i++) {
state.balances.push(34788813514 + i);
state.currentEpochParticipation.push(3);
state.previousEpochParticipation.push(7);
state.inactivityScores.push(0);
state.validators.push({
pubkey: new Uint8Array(48),
withdrawalCredentials: new Uint8Array(32),
effectiveBalance: 32e9,
slashed: false,
activationEligibilityEpoch: i,
activationEpoch: i,
exitEpoch: Infinity,
withdrawableEpoch: Infinity,
});
}
return state;
}

export function getAttestation(i: number): Attestation {
return {
aggregationBits: getBitsSingle(120, i % 120),
Expand Down Expand Up @@ -110,3 +132,13 @@ function randomBytes(bytes: number): Uint8Array {

return uint8Arr;
}

export function getOnce<T>(fn: () => T): () => T {
let value: T | null = null;
return function () {
if (value === null) {
value = fn();
}
return value;
};
}

0 comments on commit e7c4905

Please sign in to comment.