Skip to content
Open
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
81 changes: 81 additions & 0 deletions .github/workflows/perf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Perf vs Charon

on:
workflow_dispatch:
inputs:
tier:
description: "Benchmark tiers to run (1, 2, 12, or all; tier 3 needs relay connectivity)"
required: false
default: "12"
quick:
description: "Quick mode (reduced sample counts)"
type: boolean
required: false
default: false
schedule:
# Nightly at 03:17 UTC, tiers 1+2 only.
- cron: "17 3 * * *"

env:
CHARON_VERSION: v1.7.1

permissions:
contents: read

jobs:
perf:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v6

- name: Checkout charon source
uses: actions/checkout@v6
with:
repository: ObolNetwork/charon
ref: ${{ env.CHARON_VERSION }}
path: charon

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
# rust-toolchain.toml pins the actual toolchain; the action just
# bootstraps rustup.

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: charon/go.mod
cache-dependency-path: perf/go-bench/go.sum

- name: Run benchmarks
env:
TIER: ${{ inputs.tier || '12' }}
QUICK: ${{ inputs.quick && '1' || '' }}
run: |
# Gate on regressions once a blessed baseline is committed.
BASELINE=""
if [[ -f perf/baseline.json ]]; then
BASELINE="perf/baseline.json"
fi
./perf/run.sh --tier "$TIER" ${QUICK:+--quick} \
${BASELINE:+--baseline "$BASELINE"}

- name: Report to job summary
if: always()
run: |
if [[ -f perf/out/report.md ]]; then
cat perf/out/report.md >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: perf-results
path: |
perf/out/report.md
perf/out/results.json
perf/out/go-bench.txt
if-no-files-found: warn
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ test-infra/sszfixtures/sszfixtures

.claude/worktrees/
.claude/scheduled_tasks.lock
test-cluster
test-cluster
# Performance harness artifacts
/perf/out/
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ panicking_overflow_checks = "deny"
redundant_test_prefix = "deny"
unwrap_used = "deny"

# Thin LTO + a single codegen unit measured ~6-12% faster on pure-Rust hot
# paths (SSZ, k1util) with no effect on blst-bound ones; see perf/FINDINGS.md.
[profile.release]
lto = "thin"
codegen-units = 1

# Optimize crypto dependencies in test builds for faster tests
[profile.test.package.scrypt]
opt-level = 3
Expand Down
24 changes: 24 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ repository.workspace = true
license.workspace = true
publish.workspace = true

[lib]
bench = false

[features]
# Exposes internal modules and in-memory harnesses to the criterion benches.
# Never enable in production builds.
bench-util = []

[dependencies]
alloy.workspace = true
backon.workspace = true
Expand Down Expand Up @@ -44,6 +52,7 @@ url.workspace = true
[dev-dependencies]
anyhow.workspace = true
clap.workspace = true
criterion.workspace = true
rand.workspace = true
prost.workspace = true
prost-types.workspace = true
Expand All @@ -58,6 +67,21 @@ tokio = { workspace = true, features = ["test-util"] }
wiremock.workspace = true
tower = { workspace = true, features = ["util"] }

[[bench]]
name = "ssz_codec"
harness = false
path = "benches/ssz_codec.rs"

[[bench]]
name = "corepb"
harness = false
path = "benches/corepb.rs"

[[bench]]
name = "qbft"
harness = false
path = "benches/qbft.rs"

[build-dependencies]
pluto-build-proto.workspace = true
built.workspace = true
Expand Down
45 changes: 45 additions & 0 deletions crates/core/benches/corepb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Benchmarks for protobuf (de)serialization of consensus wire messages.
//!
//! Benchmark ids follow the cross-language pair naming used by
//! `perf/pairs.json`; the Go counterparts live in `perf/go-bench/`. The
//! fixture is a round-3 QBFTConsensusMsg with four justifications and a 1KiB
//! value, generated by the Go side.
#![allow(missing_docs)]

use std::{hint::black_box, time::Duration};

use criterion::{Criterion, criterion_group, criterion_main};
use pluto_core::corepb::v1::consensus::QbftConsensusMsg;
use prost::Message;

const QBFT_MSG_PB: &[u8] = include_bytes!("../../../perf/fixtures/qbft_consensus_msg.pb");

fn bench_qbft_msg(c: &mut Criterion) {
let msg = QbftConsensusMsg::decode(QBFT_MSG_PB).expect("bench setup: decode fixture");
assert_eq!(
msg.encode_to_vec(),
QBFT_MSG_PB,
"qbft msg fixture round-trip mismatch"
);

c.bench_function("tier1/proto/qbft_marshal", |b| {
b.iter(|| black_box(&msg).encode_to_vec())
});

c.bench_function("tier1/proto/qbft_unmarshal", |b| {
b.iter(|| QbftConsensusMsg::decode(black_box(QBFT_MSG_PB)).expect("decode should succeed"))
});
}

fn config() -> Criterion {
Criterion::default()
.sample_size(50)
.measurement_time(Duration::from_secs(3))
}

criterion_group! {
name = benches;
config = config();
targets = bench_qbft_msg,
}
criterion_main!(benches);
Loading
Loading