Repository navigation
Commit c93a8a8
authored
perf(beacon): write epoch balances through an in-order copy-on-write cursor (#647)
## Motivation
An epoch boundary writes every balance. `get_mut` on a tree list buffers
a copy of each element in a dense map, sorts the map into a second
`Vec`, then rebuilds every touched leaf and recomputes the root of every
written element, changed or not. The altair rewards step calls it up to
eight times per validator (four reward/penalty pairs, each as increase
then decrease), and `process_effective_balance_updates` reads
`balances()[i]` per validator, which was only cheap because the rewards
pass had just filled the map.
This PR adds an in-order lazy copy-on-write write cursor to
`ethlambda-ssz-tree` and moves the epoch balance writes onto it.
Expected gain (estimate, not measured on a real chain): tens of ms off
each epoch-start block at mainnet registry size. The import-replay
benchmark was not run for this PR.
## Changes
| commit | what |
|---|---|
| `perf(ssz-tree): add an in-order copy-on-write write cursor` |
`List`/`Vector::iter_cow`, `IterCow::next_cow`, `ElemCow::{index,
make_mut, set}`, `try_update_each`; `with_updated_leaves` generalized
over a leaf source; unit tests and model-based property tests |
| `perf(beacon): apply epoch balance and effective-balance writes
through the cursor` | `BeaconState::registry_mut` (disjoint borrows of
validators and balances); `apply_balance_deltas` used by the altair+ and
phase0 `process_rewards_and_penalties`; both
`process_effective_balance_updates`; new `tree_bench` rows; docs |
How the cursor works: it walks leaves in order and copies a leaf on the
first write that reaches it. At the leaf's end it compares only the
touched offsets with the original; if all are equal, the original leaf
(its `Arc` and cached hash) is kept. Otherwise the new leaf carries over
the element roots of unchanged elements and computes the changed ones.
On drop, one descent swaps the changed leaves in and rebuilds only their
paths. Pending buffered writes are applied before the pass starts.
## Design decisions
- `Drop` does the work, and the tree is untouched during the pass, so an
early return or a panic leaves a consistent list holding the writes so
far (tested with `catch_unwind`).
- Names as in the design: `iter_cow`, `next_cow`, `make_mut`, `set`,
`try_update_each`. No `set_with`.
- Changed composite elements get their root computed eagerly, on the
state-transition thread.
- A kept `Arc` or carried root always follows an actual comparison. The
cursor only creates fresh empty hash cells, or carried roots for a leaf
it owns; it never fills a shared `OnceLock`.
- Spec order for balances: per index, each pair is
`saturating_add(reward)` then `saturating_sub(penalty)`, pairs in order.
Deltas are never netted. Each balance depends only on its own deltas, so
index-by-index equals the spec's pair-by-pair loop.
- `apply_balance_deltas` lives in `helpers/mutators.rs` and is shared by
the phase0 and altair+ paths. It keeps the old error for a balances list
shorter than the registry, and does not touch balances past the registry
length.
- Rewards and both effective-balance passes move together: once rewards
bypass the map, the effective-balance reads would otherwise become tree
descents.
- Effective-balance updates only copy a leaf when the new effective
balance differs from the old one (it can equal it when capped at the
maximum).
- No leaf-slice API, no parallel bulk update.
## Not in this PR
- Switching `Balances` to a sparse `BTreeMap` update map: to be decided
after measuring whether a dense writer remains.
- `List::filled`: its only consumer is the participation-list work,
which is deferred.
- Migrating slashings, inactivity, and registry updates: sparse, flat
lists, or part of the epoch single-pass rewrite (#630, #632, #633).
Expect a merge conflict with #632 in `process_rewards_and_penalties`.
- Import-replay benchmark and any remote profiling.
## Testing
| command | result |
|---|---|
| `cargo test -p ethlambda-ssz-tree --profile release-fast` | 71 unit +
31 model tests passed (before the second commit) |
| `cargo test -p ethlambda-types --profile release-fast --lib` | 197
passed |
| `cargo test -p ethlambda-state-transition --profile release-fast
--lib` | 324 passed, 16 ignored |
| beacon spec suite, mainnet preset, full | 5734 passed, 4 failed |
| beacon spec suite, minimal preset, full | 40038 passed, 4 failed |
| `cargo clippy --locked --workspace --all-targets --profile
release-fast -- -D warnings` and the state-transition
`beacon-spec-tests` variant | clean |
| `cargo check -p ethlambda --profile release-fast` | ok |
The 4 spec failures in each preset are the `gossip/*` suites
(`gossip_beacon_block`, `gossip_data_column_sidecar`,
`gossip_beacon_aggregate_and_proof`, `gossip_beacon_attestation`): the
local gossip fixture directory is intentionally empty, so they match no
cases. Every other suite, including `epoch_processing`, `rewards`,
`sanity`, `finality`, `random` and `transition`, passes.
### tree_bench (2.4M validators, `--ignored`, single run)
Noisy: run while the machine was under a load average above 100 from
other builds, so absolute numbers (and even the order of the old rows)
are unreliable; read them as rough ratios only.
| row | ms |
|---|---|
| `epoch_sweep_tree` (old path) | 333.8 |
| `epoch_sweep_tree_phases` | write 58.1, apply 70.0, hash 83.1 |
| `epoch_sweep_cursor_tree` | 26.8 |
| `epoch_sweep_noop_cursor_tree` | 90.4 |
| `epoch_sweep_rebuild_tree` (rebuild baseline) | 105.8 |
| `rewards_apply_4_pairs_get_mut` | 342.5 |
| `rewards_apply_4_pairs_cursor` | 158.9 |
| `eb_updates_indexed` | 253.8 |
| `eb_updates_cursor` | 115.4 |
| `lockstep_three_lists_cursor` | 136.2 |
The bench asserts the cursor rows produce the same roots as the old
paths, and that a no-op sweep keeps the tree (`ptr_eq`).1 parent b30257c commit c93a8a8
17 files changed
Lines changed: 1400 additions & 78 deletions
File tree
- crates
- blockchain/state_transition/src/beacon
- helpers
- stf/epoch
- common
- ssz-tree
- src
- tests
- types
- src/beacon/containers
- tests
- docs
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
647 | 647 | | |
648 | 648 | | |
649 | 649 | | |
650 | | - | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
651 | 654 | | |
652 | 655 | | |
653 | 656 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
36 | 77 | | |
37 | 78 | | |
38 | 79 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
194 | 194 | | |
195 | 195 | | |
196 | 196 | | |
197 | | - | |
198 | | - | |
199 | | - | |
200 | | - | |
201 | | - | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
| |||
265 | 265 | | |
266 | 266 | | |
267 | 267 | | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
272 | 280 | | |
273 | 281 | | |
274 | 282 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
763 | 763 | | |
764 | 764 | | |
765 | 765 | | |
766 | | - | |
767 | | - | |
768 | | - | |
769 | | - | |
770 | | - | |
771 | | - | |
772 | | - | |
773 | 766 | | |
774 | 767 | | |
775 | | - | |
| 768 | + | |
| 769 | + | |
776 | 770 | | |
777 | 771 | | |
778 | 772 | | |
779 | 773 | | |
780 | | - | |
781 | | - | |
782 | | - | |
783 | | - | |
784 | | - | |
785 | | - | |
786 | | - | |
787 | | - | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
788 | 779 | | |
789 | | - | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
790 | 785 | | |
791 | | - | |
792 | | - | |
793 | | - | |
794 | | - | |
795 | | - | |
796 | | - | |
| 786 | + | |
| 787 | + | |
797 | 788 | | |
798 | 789 | | |
799 | 790 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
291 | 291 | | |
292 | 292 | | |
293 | 293 | | |
294 | | - | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
299 | | - | |
300 | | - | |
301 | 294 | | |
302 | 295 | | |
303 | | - | |
| 296 | + | |
| 297 | + | |
304 | 298 | | |
305 | 299 | | |
306 | 300 | | |
307 | 301 | | |
308 | | - | |
309 | | - | |
310 | | - | |
311 | | - | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | | - | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
316 | 308 | | |
317 | 309 | | |
318 | 310 | | |
319 | | - | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
320 | 316 | | |
321 | | - | |
322 | | - | |
323 | | - | |
324 | | - | |
325 | | - | |
326 | | - | |
| 317 | + | |
| 318 | + | |
327 | 319 | | |
328 | 320 | | |
329 | 321 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
16 | | - | |
17 | | - | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| |||
308 | 308 | | |
309 | 309 | | |
310 | 310 | | |
311 | | - | |
312 | | - | |
| 311 | + | |
| 312 | + | |
313 | 313 | | |
314 | 314 | | |
315 | 315 | | |
316 | 316 | | |
317 | 317 | | |
318 | 318 | | |
319 | 319 | | |
320 | | - | |
321 | | - | |
322 | | - | |
323 | | - | |
324 | | - | |
325 | | - | |
| 320 | + | |
| 321 | + | |
326 | 322 | | |
327 | 323 | | |
328 | 324 | | |
| |||
0 commit comments