Files
ai-app/scripts/rigs/ui-profile/tests/frame_profile.rs
T

294 lines
9.3 KiB
Rust

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;
const PASSES: usize = 8;
const VELOCITY: f32 = 12_000.0;
const PASS_CAP_MS: u64 = 4_000;
#[test]
#[ignore]
fn what_a_fling_frame_costs() {
let mut h = Harness::new(phone_size(), PHONE_SCALE);
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
h.frame(0);
h.frame(PHONE_FRAME_MS);
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",
(opened.screen.list)(&mut h.rsc).fling_velocity(),
);
let mut t = flick.end_ms();
let mut all_frames = Vec::new();
let mut all_layouts = Vec::new();
for pass in 0..PASSES {
let velocity = if pass % 2 == 0 { VELOCITY } else { -VELOCITY };
let list = (opened.screen.list)(&mut h.rsc);
list.fling(velocity);
let id = opened.screen.list.id();
h.rsc.ui.animate(id);
let mut frames = Vec::new();
let mut layouts = Vec::new();
let end = t + PASS_CAP_MS;
while t <= end {
let at = Instant::now();
h.frame(t);
frames.push(at.elapsed());
layouts.push(h.render.last_layout_duration());
t += PHONE_FRAME_MS;
if !(opened.screen.list)(&mut h.rsc).is_scrolling() {
break;
}
}
let laid_out = layouts
.iter()
.filter(|d| **d > Duration::from_micros(20))
.count();
println!(
"pass {pass} ({}): {} frames, {laid_out} did layout",
if velocity > 0.0 { "out " } else { "back" },
frames.len(),
);
summarise("frame", &frames);
all_frames.extend(frames);
all_layouts.extend(layouts);
t += 200;
}
println!(
"\nall {PASSES} passes, primitives={}",
h.render.active_primitive_count()
);
summarise("frame", &all_frames);
summarise("layout", &all_layouts);
}
#[test]
#[ignore]
fn what_a_streamed_event_costs() {
let mut h = Harness::new(phone_size(), PHONE_SCALE);
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
h.frame(0);
h.frame(PHONE_FRAME_MS);
let mut items = opened.items;
println!(
"backlog: {} items, then {} streamed events",
items.len(),
opened.stream_tail.len()
);
let mut fold = Vec::new();
let mut apply = Vec::new();
let mut frame = Vec::new();
let mut frame_same_block = Vec::new();
let mut frame_new_block = Vec::new();
let mut t = PHONE_FRAME_MS;
let block_count = |items: &[ai_app::client::transcript_fold::TranscriptItem]| {
use ai_app::client::markdown_blocks::split_blocks;
use ai_app::client::transcript_fold::TranscriptItem;
match items.last() {
Some(TranscriptItem::AssistantMsg { text, .. }) => split_blocks(text).len(),
_ => 0,
}
};
for (n, event) in opened.stream_tail.iter().enumerate() {
let blocks_before = block_count(&items);
let old = items.clone();
let at = Instant::now();
let folded = ai_app::client::transcript_fold::fold_event(&items, event);
fold.push(at.elapsed());
items = folded;
let added_block = block_count(&items) > blocks_before;
let at = Instant::now();
opened.screen.apply(&mut h.rsc, &old, &items);
apply.push(at.elapsed());
t += PHONE_FRAME_MS;
let at = Instant::now();
h.frame(t);
let took = at.elapsed();
frame.push(took);
if added_block {
frame_new_block.push(took);
} else {
frame_same_block.push(took);
}
if n == 0 || n == opened.stream_tail.len() - 1 {
println!(
" event {n:>3} of {}: items={} fold {:?} apply {:?}",
opened.stream_tail.len(),
items.len(),
fold[n],
apply[n],
);
}
}
summarise("fold", &fold);
summarise("apply", &apply);
summarise("frame", &frame);
summarise("frame/same-block", &frame_same_block);
summarise("frame/new-block", &frame_new_block);
println!(
" primitives on screen at the end: {}",
h.render.active_primitive_count()
);
}
#[test]
#[ignore]
fn what_reshaping_a_growing_message_costs() {
use iris::prelude::*;
let mut h = Harness::new(phone_size(), PHONE_SCALE);
const DELTA: &str = "the quick brown fox jumps over the lazy dog ";
let attrs = TextAttrs::default();
let width = Some(phone_size().x);
let mut buffer = iris::prelude::TextBuffer::new_empty();
let mut text = String::new();
let mut per_delta = Vec::new();
let mut total = Duration::ZERO;
for n in 1..=400 {
text.push_str(DELTA);
buffer.set_text(text.clone());
let at = Instant::now();
buffer.shape(&mut h.rsc.ui.text, &attrs, width, PHONE_SCALE);
let took = at.elapsed();
per_delta.push(took);
total += took;
if n % 100 == 0 {
println!(
" after {n:>3} deltas ({:>6} chars): this reshape {:?}",
text.len(),
took
);
}
}
println!(
" 400 deltas: {:?} of shaping in total, {:?} per delta at the end",
total,
per_delta.last().unwrap()
);
summarise("reshape", &per_delta);
println!(" at the sizes real replies reach:");
for chars in [147usize, 449, 836, 1580] {
let mut sample = String::new();
while sample.len() < chars {
sample.push_str(DELTA);
}
sample.truncate(chars);
let mut buffer = iris::prelude::TextBuffer::new(sample);
let at = Instant::now();
buffer.shape(&mut h.rsc.ui.text, &attrs, width, PHONE_SCALE);
println!(" {chars:>5} chars: {:?}", at.elapsed());
}
}
#[test]
#[ignore]
fn where_a_streamed_deltas_cost_is() {
use ai_app::client::markdown_blocks::{common_prefix, split_blocks};
const SENTENCE: &str = "The quick brown fox jumps over the lazy dog. ";
let mut src = String::new();
let mut blocks = Vec::new();
let mut split = Vec::new();
let mut compare = Vec::new();
for n in 1..=400 {
src.push_str(SENTENCE);
if n % 8 == 0 {
src.push_str("\n\n");
}
let at = Instant::now();
let new_blocks = split_blocks(&src);
split.push(at.elapsed());
let at = Instant::now();
let _ = common_prefix(&blocks, &new_blocks);
compare.push(at.elapsed());
blocks = new_blocks;
if n % 100 == 0 {
println!(
" after {n:>3} deltas ({:>6} chars, {} blocks): split {:?} compare {:?}",
src.len(),
blocks.len(),
split[n - 1],
compare[n - 1],
);
}
}
summarise("split_blocks", &split);
summarise("common_prefix", &compare);
let total: Duration = split.iter().chain(compare.iter()).sum();
println!(" 400 deltas: {total:?} in block-splitting and comparison alone");
}
#[test]
#[ignore]
fn what_the_fixture_streams() {
use ai_app::client::markdown_blocks::split_blocks;
use ai_app::client::transcript_fold::TranscriptItem;
let mut h = Harness::new(phone_size(), PHONE_SCALE);
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
let backlog = opened.items.clone();
let mut items = opened.items;
let before = items.len();
for event in &opened.stream_tail {
items = ai_app::client::transcript_fold::fold_event(&items, event);
}
println!("{} items -> {}", before, items.len());
let biggest = backlog
.iter()
.filter_map(|item| match item {
TranscriptItem::AssistantMsg { text, .. } => Some(text),
_ => None,
})
.map(|text| {
let blocks = split_blocks(text);
(
text.len(),
blocks.len(),
blocks.iter().map(|b| b.source.len()).max().unwrap_or(0),
)
})
.max_by_key(|(_, _, longest)| *longest);
if let Some((chars, blocks, longest)) = biggest {
println!(
" backlog's largest single block: {longest} chars (in a {chars}-char message of {blocks} blocks)"
);
}
for item in items.iter().rev().take(4) {
let (kind, text) = match item {
TranscriptItem::AssistantMsg { text, .. } => ("AssistantMsg", text.clone()),
TranscriptItem::UserMsg { text, .. } => ("UserMsg", text.clone()),
TranscriptItem::ToolRun { input, output, .. } => {
("ToolRun", format!("{input}{output}"))
}
_ => ("other", String::new()),
};
let blocks = split_blocks(&text);
println!(
" tail {kind}: {} chars in {} blocks; longest block {} chars",
text.len(),
blocks.len(),
blocks.iter().map(|b| b.source.len()).max().unwrap_or(0),
);
}
}