---
# System prepended metadata

title: Benchmark SSZ-QL (without proof inclusion)
tags: [EPF6]

---

# Benchmark SSZ-QL (without proof inclusion)

{%preview https://github.com/syjn99/prysm/pull/10 %}

## Introduction

After the [PR](https://github.com/OffchainLabs/prysm/pull/15888), Prysm now supports **SSZ Query** features, without Merkle proof inclusion. It means if a user wants to query 2045754th validator in mainnet, she can simply send a POST request like:

```
POST /eth/v1/beacon/states/head/query
```


With request body:

```json
{
    "query": ".validators[2045754]",
    "include_proof": false
}
```

---

I would like to share a benchmark of SSZ-QL engine and main bottleneck for the API handler in this write-up.

## Rationale when writing code for benchmarking

- As fetching a `BeaconState` from DB/memory isn't the scope of this benchmark, I simplified the handler logic, having considered that the `BeaconState` is presented. See `BenchmarkQueryBeaconState` ([Link](https://github.com/syjn99/prysm/blob/f4dfef191f31254985a030198f5446ff44ae9228/beacon-chain/rpc/prysm/beacon/benchmark_test.go#L36-L70)).
- Also, I added several isolated benchmarks for marshalling the full state, analyzing the protobuf object, and etc. See `Benchmark_A_ToProtoUnsafe`, `Benchmark_B_AnalyzeObject`, `Benchmark_C_CalculateOffsetAndLength`, `Benchmark_D_MarshalSSZ_BeaconState`, `Benchmark_E_MarshalSSZ_SSZQueryResponse` ([Link](https://github.com/syjn99/prysm/blob/f4dfef191f31254985a030198f5446ff44ae9228/beacon-chain/rpc/prysm/beacon/benchmark_test.go#L72-L166)).
- `TestPrintSszInfo` just prints the analyzed object quite prettier. See the result at [below](#Analyzed-BeaconState).

## How to run

1. Check out to `ssz-ql-benchmark` branch in `syjn99/prysm`: https://github.com/syjn99/prysm/tree/ssz-ql-benchmark
2. Download a state from [tracoor](https://tracoor.mainnet.ethpandaops.io/), and place it at `beacon-chain/rpc/prysm/beacon/state_*.go`. I used the `BeaconState` of [slot 12852789](https://beaconcha.in/slot/12852789).
3. Modify constant values on top of `beacon-chain/rpc/prysm/beacon/benchmark_test.go` if you want.

```go
const (
	stateFilePath  = "./state_12852789.ssz"
	validatorIndex = 2045754
	targetPath     = ".validators[2045754]"
)
```

4. At root directory, run this command:

```bash
go test -bench=. -run=^# -benchtime=2s beacon-chain/rpc/prysm/beacon/benchmark_test.go -cpuprofile=cpu.out -memprofile=mem.out
```

5. See the result.

## Result

```
goos: darwin
goarch: arm64
cpu: Apple M4 Pro
BenchmarkQueryBeaconState-12                          15         150825878 ns/op        933128148 B/op   6581681 allocs/op
--- BENCH: BenchmarkQueryBeaconState-12
    benchmark_test.go:177: Running one-time setup...
    benchmark_test.go:208: Setup complete.
Benchmark_A_ToProtoUnsafe-12                         186          13050653 ns/op        57977184 B/op      82690 allocs/op
Benchmark_B_AnalyzeObject-12                       64981             36990 ns/op           40984 B/op        775 allocs/op
Benchmark_C_CalculateOffsetAndLength-12         198119467               12.06 ns/op            0 B/op          0 allocs/op
Benchmark_D_MarshalSSZ_BeaconState-12                 14         145811625 ns/op        875111157 B/op   6498209 allocs/op
Benchmark_E_MarshalSSZ_SSZQueryResponse-12      58596447                40.29 ns/op          256 B/op          2 allocs/op
PASS
ok      command-line-arguments  24.499s
```

- The full process for processing the request takes about 150ms. As the result of `Benchmark_D_MarshalSSZ_BeaconState` indicates, most of the perf bottleneck is on marshalling full `BeaconState`. 
    - This was predictable: See Pros & Cons section of this approach [here](https://hackmd.io/@junsong/Byr3_lfPxl#Pros-amp-Cons6).
    - Open question: As marshalling ~300MB `BeaconState` is indeed an overhead in terms of time and memory, can we avoid/optimize it?
- `ToProtoUnsafe` takes about 13ms. I think this is a kind of good trade-off for our approach because we need a protobuf object to analyze something.
- `AnalyzeObject` takes under **50 microseconds**, which seems negligble.


## Appendix

### Analyzed `BeaconState`

```
BeaconStateElectra (Variable-size / size: 299898616)
├─ genesis_time (offset: 0) uint64 (Fixed-size / size: 8)
├─ genesis_validators_root (offset: 8) Bytes32 (Fixed-size / size: 32)
├─ slot (offset: 40) Slot (Fixed-size / size: 8)
├─ fork (offset: 48) Fork (Fixed-size / size: 16)
│  ├─ previous_version (offset: 0) Bytes4 (Fixed-size / size: 4)
│  ├─ current_version (offset: 4) Bytes4 (Fixed-size / size: 4)
│  └─ epoch (offset: 8) Epoch (Fixed-size / size: 8)
├─ latest_block_header (offset: 64) BeaconBlockHeader (Fixed-size / size: 112)
│  ├─ slot (offset: 0) Slot (Fixed-size / size: 8)
│  ├─ proposer_index (offset: 8) ValidatorIndex (Fixed-size / size: 8)
│  ├─ parent_root (offset: 16) Bytes32 (Fixed-size / size: 32)
│  ├─ state_root (offset: 48) Bytes32 (Fixed-size / size: 32)
│  └─ body_root (offset: 80) Bytes32 (Fixed-size / size: 32)
├─ block_roots (offset: 176) Vector[Bytes32, 8192] (Fixed-size / size: 262144)
├─ state_roots (offset: 262320) Vector[Bytes32, 8192] (Fixed-size / size: 262144)
├─ historical_roots (offset: 2736713) List[Bytes32, 16777216] (Variable-size / length: 758, size: 24256)
├─ eth1_data (offset: 524468) Eth1Data (Fixed-size / size: 72)
│  ├─ deposit_root (offset: 0) Bytes32 (Fixed-size / size: 32)
│  ├─ deposit_count (offset: 32) uint64 (Fixed-size / size: 8)
│  └─ block_hash (offset: 40) Bytes32 (Fixed-size / size: 32)
├─ eth1_data_votes (offset: 2760969) List[Eth1Data, 2048] (Variable-size / length: 1574, size: 113328)
├─ eth1_deposit_index (offset: 524544) uint64 (Fixed-size / size: 8)
├─ validators (offset: 2874297) List[Validator, 1099511627776] (Variable-size / length: 2113172, size: 255693812)
├─ balances (offset: 258568109) List[uint64, 1099511627776] (Variable-size / length: 2113172, size: 16905376)
├─ randao_mixes (offset: 524560) Vector[Bytes32, 65536] (Fixed-size / size: 2097152)
├─ slashings (offset: 2621712) Vector[uint64, 8192] (Fixed-size / size: 65536)
├─ previous_epoch_participation (offset: 275473485) List[uint8, 1099511627776] (Variable-size / length: 2113172, size: 2113172)
├─ current_epoch_participation (offset: 277586657) List[uint8, 1099511627776] (Variable-size / length: 2113172, size: 2113172)
├─ justification_bits (offset: 2687256) Bitvector[8] (Fixed-size / size: 1)
├─ previous_justified_checkpoint (offset: 2687257) Checkpoint (Fixed-size / size: 40)
│  ├─ epoch (offset: 0) Epoch (Fixed-size / size: 8)
│  └─ root (offset: 8) Bytes32 (Fixed-size / size: 32)
├─ current_justified_checkpoint (offset: 2687297) Checkpoint (Fixed-size / size: 40)
│  ├─ epoch (offset: 0) Epoch (Fixed-size / size: 8)
│  └─ root (offset: 8) Bytes32 (Fixed-size / size: 32)
├─ finalized_checkpoint (offset: 2687337) Checkpoint (Fixed-size / size: 40)
│  ├─ epoch (offset: 0) Epoch (Fixed-size / size: 8)
│  └─ root (offset: 8) Bytes32 (Fixed-size / size: 32)
├─ inactivity_scores (offset: 279699829) List[uint64, 1099511627776] (Variable-size / length: 2113172, size: 16905376)
├─ current_sync_committee (offset: 2687381) SyncCommittee (Fixed-size / size: 24624)
│  ├─ pubkeys (offset: 0) Vector[Bytes48, 512] (Fixed-size / size: 24576)
│  └─ aggregate_pubkey (offset: 24576) Bytes48 (Fixed-size / size: 48)
├─ next_sync_committee (offset: 2712005) SyncCommittee (Fixed-size / size: 24624)
│  ├─ pubkeys (offset: 0) Vector[Bytes48, 512] (Fixed-size / size: 24576)
│  └─ aggregate_pubkey (offset: 24576) Bytes48 (Fixed-size / size: 48)
├─ latest_execution_payload_header (offset: 296605205) ExecutionPayloadHeaderDeneb (Variable-size / size: 603)
│  ├─ parent_hash (offset: 0) Bytes32 (Fixed-size / size: 32)
│  ├─ fee_recipient (offset: 32) Bytes20 (Fixed-size / size: 20)
│  ├─ state_root (offset: 52) Bytes32 (Fixed-size / size: 32)
│  ├─ receipts_root (offset: 84) Bytes32 (Fixed-size / size: 32)
│  ├─ logs_bloom (offset: 116) Bytes256 (Fixed-size / size: 256)
│  ├─ prev_randao (offset: 372) Bytes32 (Fixed-size / size: 32)
│  ├─ block_number (offset: 404) uint64 (Fixed-size / size: 8)
│  ├─ gas_limit (offset: 412) uint64 (Fixed-size / size: 8)
│  ├─ gas_used (offset: 420) uint64 (Fixed-size / size: 8)
│  ├─ timestamp (offset: 428) uint64 (Fixed-size / size: 8)
│  ├─ extra_data (offset: 584) List[uint8, 32] (Variable-size / length: 19, size: 19)
│  ├─ base_fee_per_gas (offset: 440) Bytes32 (Fixed-size / size: 32)
│  ├─ block_hash (offset: 472) Bytes32 (Fixed-size / size: 32)
│  ├─ transactions_root (offset: 504) Bytes32 (Fixed-size / size: 32)
│  ├─ withdrawals_root (offset: 536) Bytes32 (Fixed-size / size: 32)
│  ├─ blob_gas_used (offset: 568) uint64 (Fixed-size / size: 8)
│  └─ excess_blob_gas (offset: 576) uint64 (Fixed-size / size: 8)
├─ next_withdrawal_index (offset: 2736633) uint64 (Fixed-size / size: 8)
├─ next_withdrawal_validator_index (offset: 2736641) ValidatorIndex (Fixed-size / size: 8)
├─ historical_summaries (offset: 296605808) List[HistoricalSummary, 16777216] (Variable-size / length: 810, size: 51840)
├─ deposit_requests_start_index (offset: 2736653) uint64 (Fixed-size / size: 8)
├─ deposit_balance_to_consume (offset: 2736661) Gwei (Fixed-size / size: 8)
├─ exit_balance_to_consume (offset: 2736669) Gwei (Fixed-size / size: 8)
├─ earliest_exit_epoch (offset: 2736677) Epoch (Fixed-size / size: 8)
├─ consolidation_balance_to_consume (offset: 2736685) Gwei (Fixed-size / size: 8)
├─ earliest_consolidation_epoch (offset: 2736693) Epoch (Fixed-size / size: 8)
├─ pending_deposits (offset: 296657648) List[PendingDeposit, 134217728] (Variable-size / length: 16833, size: 3231936)
├─ pending_partial_withdrawals (offset: 299889584) List[PendingPartialWithdrawal, 134217728] (Variable-size / length: 231, size: 5544)
└─ pending_consolidations (offset: 299895128) List[PendingConsolidation, 262144] (Variable-size / length: 218, size: 3488)
```