Skip to content
14 changes: 14 additions & 0 deletions .github/workflows/codspeed-repeatability-1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: CodSpeed repeatability 1

on:
pull_request:
branches: [main]

permissions:
contents: read
id-token: write

jobs:
diagnostic:
if: github.head_ref == 'bench/session-repeatability'
uses: ./.github/workflows/codspeed-repeatability-run.yml
14 changes: 14 additions & 0 deletions .github/workflows/codspeed-repeatability-2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: CodSpeed repeatability 2

on:
pull_request:
branches: [main]

permissions:
contents: read
id-token: write

jobs:
diagnostic:
if: github.head_ref == 'bench/session-repeatability'
uses: ./.github/workflows/codspeed-repeatability-run.yml
14 changes: 14 additions & 0 deletions .github/workflows/codspeed-repeatability-3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: CodSpeed repeatability 3

on:
pull_request:
branches: [main]

permissions:
contents: read
id-token: write

jobs:
diagnostic:
if: github.head_ref == 'bench/session-repeatability'
uses: ./.github/workflows/codspeed-repeatability-run.yml
62 changes: 62 additions & 0 deletions .github/workflows/codspeed-repeatability-run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: CodSpeed repeatability experiment

on:
workflow_call:

permissions:
contents: read
id-token: write

env:
CARGO_TERM_COLOR: never
MALLOC_ARENA_MAX: "1"
MALLOC_MMAP_THRESHOLD_: "131072"
MALLOC_TRIM_THRESHOLD_: "131072"
MALLOC_TOP_PAD_: "131072"

jobs:
diagnostic:
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
with:
# All three independent workflow runs build this same immutable head.
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-16
- name: Key libc debug cache
id: libc6
run: cargo xt ci workflow libc-version
- uses: actions/cache@v6
with:
path: ~/.cache/libc6-dbg
key: libc6-dbg-${{ runner.arch }}-${{ steps.libc6.outputs.version }}
- name: Install libc debug symbols
env:
LIBC6_VERSION: ${{ steps.libc6.outputs.version }}
run: cargo xt ci workflow install-libc-debug
- uses: taiki-e/install-action@v2
with:
tool: cargo-codspeed@5.0.1
- uses: Swatinem/rust-cache@v2
with:
prefix-key: Linux-cargo-codspeed-repeatability
cache-targets: "false"
- name: Build diagnostic targets
run: cargo codspeed build -m simulation -m walltime -p wacore-libsignal --bench libsignal_benchmark
- name: Record fixed source and toolchain
run: |
git rev-parse HEAD
sha256sum Cargo.lock wacore/libsignal/benches/libsignal_benchmark.rs
rustc -Vv
ldd --version
lscpu
- name: Measure the proposed batch fixtures
uses: CodSpeedHQ/action@v5
with:
runner-version: 5.4.0
mode: simulation,walltime
run: cargo codspeed run -p wacore-libsignal --bench libsignal_benchmark 'bench_session_with_self|bench_session_root_key_update'
55 changes: 55 additions & 0 deletions wacore/libsignal/benches/libsignal_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,61 @@ fn bench_sender_key_serialize_without_backlog(bencher: divan::Bencher) {
.bench_refs(|record| black_box(record.serialize().expect("serialize sender key")));
}

// A single call's simulation cost is dominated by cold cache-line placement.
// Visit independent sessions once each to amortize code-cache startup without
// repeatedly warming the same record. Keep the batch size in the benchmark ID:
// the result is a batch total, and CodSpeed's simulation adapter ignores counters.
fn session_state_batch(count: usize, is_self: bool) -> Vec<SessionState> {
let mut rng = <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(0x5345_4c46);
(0..count)
.map(|_| {
let local = IdentityKey::new(KeyPair::generate(&mut rng).public_key);
let remote = if is_self {
local
} else {
IdentityKey::new(KeyPair::generate(&mut rng).public_key)
};
SessionState::new(
3,
&local,
&remote,
&RootKey::new([0x11; 32]),
local.public_key(),
)
})
.collect()
}

// Keep one- and two-pass cases together while validating the candidate. The
// second pass is a deliberate extra-work control, not a production workload.
#[divan::bench(args = [(false, 1), (true, 1), (false, 2), (true, 2)])]
fn bench_session_with_self_batch_32(bencher: divan::Bencher, (is_self, passes): (bool, usize)) {
bencher
.with_inputs(|| session_state_batch(32, is_self))
.bench_refs(|states| {
for _ in 0..passes {
for state in states.iter() {
black_box(black_box(state).session_with_self().expect("identities"));
}
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
});
}

#[divan::bench(args = [1, 2])]
fn bench_session_root_key_update_batch_32(bencher: divan::Bencher, passes: usize) {
bencher
.with_inputs(|| session_state_batch(32, true))
.bench_refs(|states| {
for pass in 0..passes {
let key = RootKey::new([0x22 + pass as u8; 32]);
for state in states.iter_mut() {
black_box(&mut *state).set_root_key(black_box(&key));
black_box(&*state);
}
}
});
}

#[divan::bench(args = [false, true])]
fn bench_session_with_self(bencher: divan::Bencher, is_self: bool) {
bencher
Expand Down
Loading