Skip to content

docs(perf): measure allocation on the group stanza build - #1282

Merged
jlucaso1 merged 4 commits into
mainfrom
perf/group-allocs
Aug 11, 2026
Merged

docs(perf): measure allocation on the group stanza build#1282
jlucaso1 merged 4 commits into
mainfrom
perf/group-allocs

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

An external profile put a group send at 387 allocations / 87.7 KiB per message at 128 members and named send::encrypt::ensure_sessions_for_devices as the place to start.

This records what divan::AllocProfiler actually measures on this repository's send benches, and — after review — what those numbers do not establish. The first revision of this PR claimed they refuted the 387; they do not, and the doc now says so explicitly.

The result that survives: a group send that distributes no sender key is flat in group size — 22 allocations and 3.58 KB at 10, 50 and 256 members.

Changes

File Change
agent_docs/observability.md new section — Per-message allocation on the group stanza build

No code changes.

Cost

divan::AllocProfiler as global allocator in send_receive_benchmark.rs, 50 samples, pinned to one core. Divan tallies only the benchmarked closure.

Scope, stated first because it bounds every row: run_group_send calls prepare_group_stanza and marshals the node. The client send path around it — group lookup, retry caching, sender-key cache access, resolve_skdm_targets_memoized, persistence-adapter construction, the send itself — is outside the measured region.

stanza build allocations bytes
bench_dm_send 157 27.9 KB
bench_group_send_10 (no distribution) 22 3.58 KB
bench_group_send_50 (no distribution) 22 3.58 KB
bench_group_send_256 (no distribution) 22 3.58 KB
bench_group_send_skdm_256 (distributing) 6,816 675.1 KB

Instruction counts unchanged — nothing executable is touched.

Flat across a 25× range in membership, because the stanza carries one <enc type="skmsg"> for the whole group and nothing per recipient (pinned separately by warm_group_send_encoding_scale, #1281). The DM row is higher because a DM pairwise-encrypts once per recipient device.

Checked and not changed

Four claims from the first revision were withdrawn after review; each is now documented as a limit rather than a finding:

  • "387 is not reproducible here." Withdrawn — different scope. 387 came from a full client send; 22 is stanza build plus marshal. The difference is real, unmeasured work.
  • "A warm send never calls ensure_sessions_for_devices." Withdrawn — false for the ordinary linked account. src/send/mod.rs states in the initial_targets match that own devices are never memoized warm, so own-only SKDM needs is the warm steady state: such a send carries a nonempty distribution_list on every message. The 22 figure is the zero-own-target case, and the doc now labels it that way.
  • "6,816 = one X3DH plus one pairwise encrypt per device." Withdrawn — setup_group_send calls establish_session for every member before forcing distribution, so ensure_sessions_for_devices finds each session present and never reaches the prekey-fetch branch. The figure is the SKDM encrypt fan-out plus stanza build (~26.6 allocations per target). No cold-session cost is measured anywhere in this repository, and the doc says so rather than implying one.
  • "resolve_skdm_targets_memoized minimizes redistribution." Withdrawn — it memoizes device-set resolution. Which devices still need the key is decided by filter_skdm_targets against the SenderKeyDeviceMap (device_and_primary_warm). The doc now points at that map instead.
  • Amortization arithmetic corrected: one ~3.4K distribution plus nine 22-allocation sends averages ~360, not 387. The shape is suggestive; the decomposition is not claimed.
  • resolve_skdm_targets_memoized untouched in code, per the brief.
  • AllocProfiler deliberately not committed to send_receive_benchmark.rs — swapping the global allocator shifts the timing of every bench in that file and would step every one of their CodSpeed series for a number wanted occasionally. voip_benchmark.rs and prekey_store_benchmark.rs carry it because there the churn is the thing under test. The doc carries the three lines to add it back.

Validation

  • Figures reproduced from a clean --profile bench build of wacore's send_receive_benchmark, taskset-pinned, 50 samples per bench.
  • Working tree is byte-identical to main outside the doc: the AllocProfiler line was reverted after measuring.
  • Each withdrawn claim was re-checked against the source before withdrawal (src/send/mod.rs initial_targets match and filter_skdm_targets; setup_group_send in the bench; run_group_send's call graph).
  • No test changes — no behavior change to test.

An external profile put a group send at 387 allocations and 87.7 KiB per
message against 166 and 39.1 KiB for a DM, and pointed at
`ensure_sessions_for_devices` as the place to start. Measured here with
`divan::AllocProfiler` over the existing send benches, the group path is not
expensive and does not grow with group size:

| send                                | allocs/msg | bytes/msg |
| ----------------------------------- | ---------: | --------: |
| `bench_dm_send`                     |        157 |   27.9 KB |
| `bench_group_send_10`   (warm)      |         22 |   3.58 KB |
| `bench_group_send_50`   (warm)      |         22 |   3.58 KB |
| `bench_group_send_256`  (warm)      |         22 |   3.58 KB |
| `bench_group_send_skdm_256` (cold)  |      6,816 |  675.1 KB |

Flat from 10 to 256 members, and seven times cheaper than a DM -- which follows
from the shapes once they are side by side: a DM pairwise-encrypts per recipient
device, a warm group send does one sender-key encrypt for everyone. The group
path is cheaper per message precisely because sender keys exist.

So 387 is not a per-message cost, it is an average. A distributing send is 6,816
allocations at 256 targets (~26.6 per device: one X3DH plus one pairwise
encrypt, inherent -- each device needs its own copy of the sender key under its
own ratcheting session). At 128 members that is ~3.4K, so redistributing about
every ten messages averages to 387. The lever a reader would take from that
number -- cut allocations in the send path -- is the wrong one; the quantity
that moves it is redistribution frequency, which `resolve_skdm_targets_memoized`
already minimizes and which is correctness, not a performance knob.

`ensure_sessions_for_devices` is named explicitly because a profile points
there: it is called from inside the `distribution_list` branch only, so a warm
send never enters it, and its cost in an averaged profile is entirely amortized
cold-path cost.

No code changes. The `AllocProfiler` used to measure this is deliberately not
checked into `send_receive_benchmark.rs` -- swapping the global allocator shifts
the timing of every bench in that file, which would put a one-time step through
each of their CodSpeed series for a number wanted only occasionally. The doc
carries the three lines needed to reproduce it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019JXjxfWyfvxB6WEgiCLVF3
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jlucaso1, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0bf5286e-96da-4d7b-863e-4c7b327115ca

📥 Commits

Reviewing files that changed from the base of the PR and between ad81962 and 0095fc0.

📒 Files selected for processing (1)
  • agent_docs/observability.md
📝 Walkthrough

Summary by CodeRabbit

  • Documentation
    • Added observability documentation for per-message memory allocation measurements.
    • Documented allocation behavior for direct messages, warm group messages, and sender-key distribution.
    • Explained how recipient count and redistribution frequency affect allocation costs.
    • Noted the temporary use of an allocation profiler during benchmarking.

Walkthrough

The documentation adds per-message allocation measurements for direct messages, warm groups, and sender-key distribution. It explains steady-state costs, redistribution scaling, redistribution frequency, and temporary allocator instrumentation.

Changes

Allocation observability

Layer / File(s) Summary
Document allocation measurements
agent_docs/observability.md
Documents allocation counts and bytes for direct, warm-group, and sender-key distribution sends. It distinguishes steady-state costs from redistribution costs and records the temporary use of divan::AllocProfiler.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related PRs

Suggested labels: performance

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the documentation change and its focus on group-stanza allocation measurement.
Description check ✅ Passed The description directly explains the documented allocation measurements, scope, validation, and absence of code changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/group-allocs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown

Greptile Summary

The PR documents allocation measurements for group stanza construction and carefully limits their interpretation to the benchmarked closure.

  • Records allocation counts for direct-message, non-distributing group, and first-message SKDM stanza builds.
  • Clarifies that linked-account warm sends still distribute sender keys to own devices and therefore invoke session preparation.
  • Distinguishes group members from resolved device targets and documents how to reproduce the measurements.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
agent_docs/observability.md Adds a scoped allocation analysis whose revised warm-send explanation is consistent with the current send pipeline and benchmark fixture.

Reviews (3): Last reviewed commit: "docs(perf): label the distributing row a..." | Re-trigger Greptile

Comment thread agent_docs/observability.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent_docs/observability.md`:
- Around line 376-382: Update the warm group send description to call it “the
cheapest measured send path” instead of “the cheapest thing this client does,”
keeping the allocation figures and surrounding comparison unchanged.
- Around line 388-393: Correct the amortization example in the observability
documentation: one approximately 3,400-allocation redistribution plus nine
22-allocation warm sends averages about 360 allocations per message, not 387.
Either change the stated average to approximately 360 or explicitly document the
additional cost required to justify 387, while preserving the surrounding
warm/distribution context.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c555cef5-4bf6-4df7-92d4-2ba9668f96db

📥 Commits

Reviewing files that changed from the base of the PR and between 8f2beb7 and ad81962.

📒 Files selected for processing (1)
  • agent_docs/observability.md

Comment thread agent_docs/observability.md Outdated
Comment thread agent_docs/observability.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad8196260b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread agent_docs/observability.md Outdated
Comment thread agent_docs/observability.md Outdated
Comment thread agent_docs/observability.md Outdated
Comment thread agent_docs/observability.md Outdated
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 10.04 MiB 10.04 MiB 0
bin .text 8.05 MiB 8.05 MiB 0
bin allocated (text+data+bss) 10.04 MiB 10.04 MiB 0
llvm-lines wacore 533,670 533,670 0
llvm-lines wacore copies 17,422 17,422 0
llvm-lines whatsapp-rust lib 762,401 762,401 0
llvm-lines whatsapp-rust lib copies 23,771 23,771 0
deps crates (Cargo.lock) 462 462 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.84 MiB 1.84 MiB 0
.text wacore 693.00 KiB 693.00 KiB 0
.text wacore_binary 81.61 KiB 81.61 KiB 0
.text wacore_libsignal 178.98 KiB 178.98 KiB 0
.text wacore_appstate 22.35 KiB 22.35 KiB 0
.text wacore_noise 20.94 KiB 20.94 KiB 0
.text waproto 1.74 MiB 1.74 MiB 0
.text whatsapp_rust_sqlite_storage 540.56 KiB 540.56 KiB 0
.text whatsapp_rust_tokio_transport 40.49 KiB 40.49 KiB 0
.text whatsapp_rust_ureq_http_client 12.68 KiB 12.68 KiB 0
.text std 995.62 KiB 995.62 KiB 0
.text other deps 1.90 MiB 1.90 MiB 0

Baseline: 5fdf3cca1 (latest main run) · Head: 3359fc137 · Graphs

Review caught four claims that the measurement does not support. The numbers
stand; the conclusions drawn around them did not.

The benchmark is not the client send path. `run_group_send` calls
`prepare_group_stanza` and marshals the node -- the group lookup, retry caching,
sender-key cache access, `resolve_skdm_targets_memoized` and persistence work in
`src/send/mod.rs` are all outside the measured region. Comparing 22 against a
whole-client 387 was comparing two different quantities, so the section no longer
claims 387 is unreproducible. Retitled to say what is measured.

"A warm send never calls `ensure_sessions_for_devices`" is false for the ordinary
linked account. `src/send/mod.rs` states it directly in the `initial_targets`
match: own devices are never memoized warm, so own-only SKDM needs IS the warm
steady state and such a send carries a nonempty `distribution_list` on every
message. The 22-allocation figure is the zero-own-target case and now says so.

The distributing row is not X3DH. `setup_group_send` establishes a session for
every member before forcing distribution, so `ensure_sessions_for_devices` finds
them present and never reaches the prekey-fetch branch. 6,816 is the SKDM
encrypt fan-out plus stanza build, ~26.6 allocations per target -- not one X3DH
plus one encrypt each, and not a cost this repository has measured cold.

`resolve_skdm_targets_memoized` does not govern redistribution frequency. It
memoizes device-set resolution; `filter_skdm_targets` against the
`SenderKeyDeviceMap` decides which devices still need the key. Calling a lookup
cache the correctness mechanism pointed readers at the wrong state.

Also: the amortization example averages ~360, not 387 (one ~3.4K distribution
plus nine 22-allocation sends over ten messages), and "the cheapest thing this
client does" is narrowed to the paths actually measured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019JXjxfWyfvxB6WEgiCLVF3
@jlucaso1 jlucaso1 changed the title docs(perf): measure per-message allocation on the send paths docs(perf): measure allocation on the group stanza build Aug 11, 2026
greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 11, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fbf4c37a82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread agent_docs/observability.md
Comment thread agent_docs/observability.md Outdated
Comment thread agent_docs/observability.md Outdated
claude added 2 commits August 11, 2026 17:47
Brings in #1281, whose `warm_group_send_encoding_scale` this section cites.
The reference dangled while this branch sat on a pre-merge base.
Three more from review, all confirmed against the fixtures.

The cited regression test was not in this branch's tree. `perf/group-allocs`
was cut before #1281 landed, so `warm_group_send_encoding_scale` existed only on
main and the reference dangled for anyone reading the PR. Merged main in.

The distributing row is a first-message fan-out, not a steady rotation.
`establish_session` runs `process_prekey_bundle` alone -- unlike
`establish_bidirectional` it never completes the round trip -- so every session
still carries its `pending_pre_key` and each SKDM encryption emits a `pkmsg`,
with prekey wrapping and device-identity serialization attached. 6,816 is that
shape. A later rotation or reset over acknowledged sessions emits plain
`SignalMessage`s and costs less; nothing here measures it. Labelled in the table
and in the prose, replacing a paragraph that implied the figure covered
redistribution generally.

The extrapolation was in the wrong unit. `setup_group_send(n)` creates exactly
one device per member, while SKDM fan-out scales with resolved devices -- a real
group resolves to more targets than members. The ~3.4K figure is now stated per
128 *targets*, and the comparison notes that the external number is quoted in
members whose device count is unknown, which is one more reason the
decomposition is not claimable from here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019JXjxfWyfvxB6WEgiCLVF3
@greptile-apps
greptile-apps Bot dismissed their stale review August 11, 2026 17:48

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@jlucaso1
jlucaso1 merged commit 575c724 into main Aug 11, 2026
19 of 20 checks passed
@jlucaso1
jlucaso1 deleted the perf/group-allocs branch August 11, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants