Prune commentary and stale Rust port notes
This commit is contained in:
1 parent
5428cd75c9
commit
25370731d0
193 files changed
+693
-16219
No files matched your search
@@ -1,44 +1,11 @@
|
||||
//! Profiling runs rather than tests: what a frame costs on the CPU, at
|
||||
//! layer 1 (docs/RUST.md's "Three test layers") -- the real
|
||||
//! transcript screen over the real bench fixture, with no window, no
|
||||
//! compositor and no GPU, on a clock this file owns. It exists so "the
|
||||
//! fling stutters" can be attributed rather than guessed at, and it is
|
||||
//! kept between investigations rather than rewritten each time (Iris,
|
||||
//! 2026-09-09: "please keep the profiling rig around for future use").
|
||||
//!
|
||||
//! cargo test --release --test frame_profile -- --ignored --nocapture
|
||||
//!
|
||||
//! from `scripts/rigs/ui-profile/`.
|
||||
//!
|
||||
//! Two runs today: `what_a_fling_frame_costs` (scrolling over transcript
|
||||
//! that is already folded) and `what_a_streamed_event_costs` (a reply
|
||||
//! arriving into it).
|
||||
//!
|
||||
//! `#[ignore]`d because it asserts nothing -- it prints a distribution,
|
||||
//! so `run-tests.sh` neither runs it nor can fail on it. **Release, or
|
||||
//! the numbers mean nothing**: layout is dominated by text shaping, which
|
||||
//! is an order of magnitude slower unoptimised.
|
||||
//!
|
||||
//! What it cannot answer: anything about the GPU, the present queue, or
|
||||
//! the phone's own clock. It measures the CPU half of a frame, which is
|
||||
//! where `cpu_p50` in a phone bench report comes from.
|
||||
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::{Harness, TouchScript};
|
||||
use iris::prelude::*;
|
||||
use std::time::{Duration, Instant};
|
||||
use ui_profile::stats::summarise;
|
||||
|
||||
/// Passes over the same content, alternating direction. More than two
|
||||
/// because the question the rig was built for is whether a frame's cost
|
||||
/// is first-time work (which the first pass pays and the rest do not) or
|
||||
/// work repeated every time a row comes back on screen.
|
||||
const PASSES: usize = 8;
|
||||
/// The velocity `bench_client.rs`'s fling phase uses, so a number here
|
||||
/// and a number in a phone report describe the same gesture.
|
||||
const VELOCITY: f32 = 12_000.0;
|
||||
/// A fling settles on the spline's own schedule (~2s at this velocity);
|
||||
/// this only stops a pass that somehow never settles from running away.
|
||||
const PASS_CAP_MS: u64 = 4_000;
|
||||
|
||||
#[test]
|
||||
@@ -49,9 +16,8 @@ fn what_a_fling_frame_costs() {
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
|
||||
// The recorded flick first, so the velocity a real finger produces is
|
||||
// in the log beside the scripted passes below.
|
||||
let flick = TouchScript::parse(include_str!("../../../../app-rust/touch/flick-120hz.touch")).unwrap();
|
||||
let flick =
|
||||
TouchScript::parse(include_str!("../../../../app-rust/touch/flick-120hz.touch")).unwrap();
|
||||
h.replay(&flick);
|
||||
println!(
|
||||
"recorded flick released at {:?}px/s; scripted passes run at {VELOCITY}px/s",
|
||||
@@ -62,8 +28,6 @@ fn what_a_fling_frame_costs() {
|
||||
let mut all_frames = Vec::new();
|
||||
let mut all_layouts = Vec::new();
|
||||
for pass in 0..PASSES {
|
||||
// Away from the newest end on the even passes and back on the
|
||||
// odd ones, the same out-and-back the bench's fling phase drives.
|
||||
let velocity = if pass % 2 == 0 { VELOCITY } else { -VELOCITY };
|
||||
let list = (opened.screen.list)(&mut h.rsc);
|
||||
list.fling(velocity);
|
||||
@@ -95,7 +59,6 @@ fn what_a_fling_frame_costs() {
|
||||
summarise("frame", &frames);
|
||||
all_frames.extend(frames);
|
||||
all_layouts.extend(layouts);
|
||||
// A moment at rest between passes, as a finger would leave.
|
||||
t += 200;
|
||||
}
|
||||
|
||||
@@ -107,15 +70,6 @@ fn what_a_fling_frame_costs() {
|
||||
summarise("layout", &all_layouts);
|
||||
}
|
||||
|
||||
/// The other half of a bench run, and since 2026-09-09 the expensive one:
|
||||
/// what it costs to fold one arriving event into the transcript and show
|
||||
/// it. The bench's stream phase measured `build p50 9.5ms` on Iris's
|
||||
/// phone against a fling's 0.4ms, so this is where the frame time now is.
|
||||
///
|
||||
/// Reports the fold and the widget-tree apply separately, because they
|
||||
/// are different problems with different fixes -- and reports how the
|
||||
/// cost moves as the transcript grows, which is the shape that says
|
||||
/// whether the work is per-event or per-event-times-transcript.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn what_a_streamed_event_costs() {
|
||||
@@ -134,8 +88,6 @@ fn what_a_streamed_event_costs() {
|
||||
let mut fold = Vec::new();
|
||||
let mut apply = Vec::new();
|
||||
let mut frame = Vec::new();
|
||||
// Split by whether the delta started a new markdown block, since that
|
||||
// is the delta that builds a widget rather than re-shaping one.
|
||||
let mut frame_same_block = Vec::new();
|
||||
let mut frame_new_block = Vec::new();
|
||||
let mut t = PHONE_FRAME_MS;
|
||||
@@ -171,8 +123,6 @@ fn what_a_streamed_event_costs() {
|
||||
frame_same_block.push(took);
|
||||
}
|
||||
|
||||
// Where the cost sits as the transcript grows -- one line early,
|
||||
// one late, is enough to see a per-event cost from a quadratic.
|
||||
if n == 0 || n == opened.stream_tail.len() - 1 {
|
||||
println!(
|
||||
" event {n:>3} of {}: items={} fold {:?} apply {:?}",
|
||||
@@ -189,36 +139,18 @@ fn what_a_streamed_event_costs() {
|
||||
summarise("frame", &frame);
|
||||
summarise("frame/same-block", &frame_same_block);
|
||||
summarise("frame/new-block", &frame_new_block);
|
||||
// What the GPU side has to carry, which layer 1 builds but never
|
||||
// uploads and so cannot time: every primitive is re-uploaded whenever
|
||||
// the arena changes, and the buffer is recreated when its length does
|
||||
// (`ArrBuf::update`). Splitting the streamed reply into blocks trades
|
||||
// shaping cost for more widgets, so this is the number that says
|
||||
// whether that trade is free on a real GPU path.
|
||||
println!(
|
||||
" primitives on screen at the end: {}",
|
||||
h.render.active_primitive_count()
|
||||
);
|
||||
}
|
||||
|
||||
/// What re-shaping a *growing* message costs, isolated from everything
|
||||
/// else a frame does -- the measurement that decides whether an
|
||||
/// incremental-text design would pay for itself (Iris, 2026-09-09:
|
||||
/// "we should definitely investigate incremental text rendering").
|
||||
///
|
||||
/// Grows one text buffer a delta at a time, the way a streamed reply
|
||||
/// grows one row, and reports what `TextBuffer::shape` costs at each
|
||||
/// length. Linear per-delta cost means the total over a reply is
|
||||
/// quadratic in its length, which is the thing an incremental shaper
|
||||
/// would remove.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn what_reshaping_a_growing_message_costs() {
|
||||
use iris::prelude::*;
|
||||
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
// A reply-sized paragraph built a delta at a time. The deltas are
|
||||
// words rather than characters because that is what a model streams.
|
||||
const DELTA: &str = "the quick brown fox jumps over the lazy dog ";
|
||||
let attrs = TextAttrs::default();
|
||||
let width = Some(phone_size().x);
|
||||
@@ -250,13 +182,6 @@ fn what_reshaping_a_growing_message_costs() {
|
||||
);
|
||||
summarise("reshape", &per_delta);
|
||||
|
||||
// The same measurement at the sizes real replies actually reach.
|
||||
// Measured 2026-09-09 over 7,706 top-level blocks from 3,675 real
|
||||
// assistant messages on this machine: p50 147 chars, p90 449, p99
|
||||
// 836, largest 1,580, and *nothing* above 4,000. The bench fixture's
|
||||
// streamed message is one 14,888-character block, which is 9x the
|
||||
// largest real one -- so the sizes below are what a live reshape
|
||||
// actually costs and the run above is what the benchmark measures.
|
||||
println!(" at the sizes real replies reach:");
|
||||
for chars in [147usize, 449, 836, 1580] {
|
||||
let mut sample = String::new();
|
||||
@@ -271,19 +196,11 @@ fn what_reshaping_a_growing_message_costs() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a streamed delta's cost actually is, given that `RowBlocks::
|
||||
/// apply_delta` already re-shapes only the block the delta landed in.
|
||||
/// Three candidates, all of which scale with the *whole* message rather
|
||||
/// than the delta: re-parsing the markdown to find the blocks, comparing
|
||||
/// them against the ones already drawn, and re-shaping the last block.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn where_a_streamed_deltas_cost_is() {
|
||||
use ai_app::client::markdown_blocks::{common_prefix, split_blocks};
|
||||
|
||||
// A reply with real block structure -- paragraphs separated by blank
|
||||
// lines, the way a model writes -- so the last block is one paragraph
|
||||
// rather than the whole message.
|
||||
const SENTENCE: &str = "The quick brown fox jumps over the lazy dog. ";
|
||||
let mut src = String::new();
|
||||
let mut blocks = Vec::new();
|
||||
@@ -292,8 +209,6 @@ fn where_a_streamed_deltas_cost_is() {
|
||||
let mut compare = Vec::new();
|
||||
for n in 1..=400 {
|
||||
src.push_str(SENTENCE);
|
||||
// A paragraph break every eight deltas, so the trailing block
|
||||
// stays a normal size and only the message grows.
|
||||
if n % 8 == 0 {
|
||||
src.push_str("\n\n");
|
||||
}
|
||||
@@ -323,8 +238,6 @@ fn where_a_streamed_deltas_cost_is() {
|
||||
println!(" 400 deltas: {total:?} in block-splitting and comparison alone");
|
||||
}
|
||||
|
||||
/// What the bench fixture's streamed tail actually is, since the cost of
|
||||
/// a delta depends entirely on how big the block it lands in gets.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn what_the_fixture_streams() {
|
||||
@@ -340,8 +253,6 @@ fn what_the_fixture_streams() {
|
||||
items = ai_app::client::transcript_fold::fold_event(&items, event);
|
||||
}
|
||||
println!("{} items -> {}", before, items.len());
|
||||
// The stress message the generator plants in the backlog: one block,
|
||||
// no blank line, just under `text_cap`'s MESSAGE_BYTES.
|
||||
let biggest = backlog
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
@@ -362,8 +273,6 @@ fn what_the_fixture_streams() {
|
||||
" backlog's largest single block: {longest} chars (in a {chars}-char message of {blocks} blocks)"
|
||||
);
|
||||
}
|
||||
// The last few items are where the stream landed. Only the message
|
||||
// variants matter -- those are what a delta appends to.
|
||||
for item in items.iter().rev().take(4) {
|
||||
let (kind, text) = match item {
|
||||
TranscriptItem::AssistantMsg { text, .. } => ("AssistantMsg", text.clone()),
|
||||
|
||||
Reference in new issue
Block a user