From dff76228f103f36118f19c4492e20cd46f234fb8 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 8 Nov 2026 19:40:54 +0000 Subject: access: read once what rules read at the same heads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bench_screening shows judging at about 17 µs of a change's 25: some two and a half reads at its heads (the policy, bans, its node's member entry, whether there is a registry, the sealed declarations), each costing as much as working out what the heads cover. Screening judges most of a batch at the same heads, and those reads are the same for every change judged there. Keep them (rules::Reads), by node and by user where they depend on one, while the heads stay the same; screening keeps one for a batch. What the heads hold doesn't change as the document takes changes, so nothing goes stale but by other heads. The benchmark breakdown shows judging so and afresh. --- src/document/bench.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) (limited to 'src/document') diff --git a/src/document/bench.rs b/src/document/bench.rs index 026cf7b..2b82036 100644 --- a/src/document/bench.rs +++ b/src/document/bench.rs @@ -684,9 +684,10 @@ fn screening_costs(workload: &str, doc: &Document) { } /// Where screening `changes` spends its time, per change judged: decoding them, their -/// operations placed (`inspect::Inspector`), and judging them (`rules::judge`), of which -/// reading the policy (`policy::of`) and, for scale, one read (the committer's member -/// entry). As screening judges most: the first ones in (the maps they make, which flush +/// operations placed (`inspect::Inspector`), and judging them, as screening does (what +/// is read at the same heads read once, `rules::Reads`) and afresh (`rules::judge`); of +/// that, reading the policy (`policy::of`) and, for scale, one read (the committer's +/// member entry). As screening judges most: the first ones in (the maps they make, which flush /// the batch), the rest judged at the heads after them (`screen`'s frontier). fn screening_parts(changes: &[automerge::Change]) { use crate::access::{policy, rules}; @@ -736,26 +737,28 @@ fn screening_parts(changes: &[automerge::Change]) { .iter() .map(|ops| ops.iter().map(|op| op.path.clone()).collect()) .collect(); - let judge = median(3, || { + let judge_all = |reads: &mut Option| { let started = Instant::now(); for (index, change) in rest.iter().enumerate() { - let judged = rules::judge( - &base, - Epoch::GENESIS, - &rules::Change { - hash: change.hash(), - deps: change.deps(), - seen: &seen, - committer: committers[index].as_deref(), - user: None, - paths: &paths[index], - ops: &ops[index], - }, - ); + let change = rules::Change { + hash: change.hash(), + deps: change.deps(), + seen: &seen, + committer: committers[index].as_deref(), + user: None, + paths: &paths[index], + ops: &ops[index], + }; + let judged = match reads { + Some(reads) => rules::judge_reading(&base, Epoch::GENESIS, &change, reads), + None => rules::judge(&base, Epoch::GENESIS, &change), + }; assert_eq!(judged, Ok(()), "every change taken"); } started.elapsed() - }); + }; + let judge = median(3, || judge_all(&mut Some(rules::Reads::default()))); + let afresh = median(3, || judge_all(&mut None)); let read_policy = median(3, || { let started = Instant::now(); for _ in rest { @@ -775,11 +778,12 @@ fn screening_parts(changes: &[automerge::Change]) { }); let per = |d: Duration| d.as_secs_f64() * 1e6 / rest.len().max(1) as f64; println!( - "{:<40} per change: decode {:.2} µs, place {:.2} µs, judge {:.2} µs (policy::of {:.2} µs, one read {:.2} µs)", + "{:<40} per change: decode {:.2} µs, place {:.2} µs, judge {:.2} µs (afresh {:.2} µs: policy::of {:.2} µs, one read {:.2} µs)", "", per(decode), per(place), per(judge), + per(afresh), per(read_policy), per(one_read), ); -- cgit v1.3.1