docs: refresh README and CLAUDE.md; add ingest benchmark
The CLAUDE.md architecture section still described the pre-redesign engine: its data flow named `Batch`, `Agent`, `Player` and `message.rs`, none of which have existed since T2, and the public API it listed did not match `lib.rs`. It is the first thing a fresh session reads, so it was actively misleading. Rewritten against the current module layout, with the invariants that are easy to violate — ties needing a positive `p_draw`, NaN never being convergence, log-space evidence, color contiguity, `forbid(unsafe_code)`, and ingestion-order equivalence — written down. The README Todo list had five entries that were already done, including "Time needs to be an enum": `Time` has been a trait since T2, and the `batch::compute_elapsed()` it pointed at no longer exists. The genuinely open item — cross-checking `quality()` against sublee/trueskill — stays. `benches/ingest.rs` measures one-event-per-call against a single batched call. The rest of the suite only measured batched construction, which is why the quadratic fixed earlier on this branch went unnoticed for so long. `TimeSlice::log_evidence` also hashes its target set once instead of scanning the slice per player per event, so `log_evidence_for` with many keys is no longer quadratic. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnsaJg74eNSva3PJjK2eej
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
//! Ingestion cost: one event per call versus one batched call.
|
||||
//!
|
||||
//! The rest of the suite only measures batched construction, which is why a
|
||||
//! quadratic in the incremental path went unnoticed — `record_winner` and
|
||||
//! `event(..).commit()` each ingest a single event, so a caller looping over a
|
||||
//! match feed takes that path.
|
||||
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
|
||||
use smallvec::smallvec;
|
||||
use trueskill_tt::{Event, History, Member, Outcome, Team};
|
||||
|
||||
fn events(n: usize, time: i64) -> Vec<Event<i64, String>> {
|
||||
(0..n)
|
||||
.map(|i| Event {
|
||||
time,
|
||||
teams: smallvec![
|
||||
Team::with_members([Member::new(format!("p{}", 2 * i))]),
|
||||
Team::with_members([Member::new(format!("p{}", 2 * i + 1))]),
|
||||
],
|
||||
outcome: Outcome::winner(0, 2),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn bench_ingest(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("ingest");
|
||||
|
||||
for n in [250usize, 500, 1000] {
|
||||
group.bench_with_input(BenchmarkId::new("one-at-a-time", n), &n, |b, &n| {
|
||||
b.iter_batched(
|
||||
|| events(n, 0),
|
||||
|evs| {
|
||||
let mut h: History<i64, _, _, String> = History::builder_with_key().build();
|
||||
for ev in evs {
|
||||
h.add_events(std::iter::once(ev)).unwrap();
|
||||
}
|
||||
black_box(h.time_slices_len())
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("single-batch", n), &n, |b, &n| {
|
||||
b.iter_batched(
|
||||
|| events(n, 0),
|
||||
|evs| {
|
||||
let mut h: History<i64, _, _, String> = History::builder_with_key().build();
|
||||
h.add_events(evs).unwrap();
|
||||
black_box(h.time_slices_len())
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_ingest);
|
||||
criterion_main!(benches);
|
||||
Reference in New Issue
Block a user