Two things the measurements for the previous commit turned up. Highlighting a fence cost 174ms for a two-hundred-line Kotlin block, and the lazy list charged it again every time that block scrolled back into composition -- six times in one bench run, with the scroll's draw phase at 1.29ms per frame. So it is warmed and cached where parses already are: `highlight` is a plain function taking no colour from the theme, `warm` fills `ParsedReplies.highlighted` from `fences(parse)` off the drawing thread, and `fenceContent` extracts the code here rather than through the library's composable, so the string warmed is the string drawn. And the anchor restore asked for a span counted in events, which goes negative when the anchor's row is the oldest half-row and was coerced to one -- a request per delta, six hundred round trips walking one reply back a word at a time with the spinner up. It asks for a page of rows now. Clean pairs, fresh sessions each side, same gestures. Fence scroll (transcript-bench.sh): draw phase 0.74ms per frame before highlighting existed, 0.77ms after, no lexing in the window either side. Streaming forty linked items (stream-bench.sh): 2412ms of reparsing before, 674ms after; mean 5.0ms to 1.4ms, worst 8.9ms to 7.9ms. Bullet glyphs, fence colours, the image links and the reference link checked on the emulator; lint clean on AGP 9.4.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
96 lines
3.7 KiB
Bash
Executable File
96 lines
3.7 KiB
Bash
Executable File
#!/bin/sh
|
|
# Streams a message into the sandbox session on screen and prints the app's
|
|
# render report for the time it took -- the standard measurement for "is a
|
|
# reply that is still arriving cheap", the way transcript-bench.sh is for
|
|
# scrolling one that has settled.
|
|
#
|
|
# ./stream-bench.sh FILE restart the app, open the first
|
|
# session, stream FILE into it, report
|
|
# ./stream-bench.sh -k FILE keep whatever session is open now
|
|
#
|
|
# The session is the first one the sandbox lists (ui-sandbox.sh spawn makes
|
|
# one), and the file is echoed back a word at a time, which is the shape a
|
|
# real reply arrives in. The numbers to read are `markdown reparsed while
|
|
# streaming` -- how many times, and how long each -- and the worst
|
|
# `record: one block`; both are proportional to how much of the reply the
|
|
# tail reparse has to cover, which is what the live parse exists to bound.
|
|
set -eu
|
|
cd "$(dirname "$0")"
|
|
. ./android-env.sh >/dev/null 2>&1
|
|
|
|
keep=""
|
|
while getopts k flag; do
|
|
case $flag in
|
|
k) keep=1 ;;
|
|
*) exit 2 ;;
|
|
esac
|
|
done
|
|
shift $((OPTIND - 1))
|
|
file=${1:?usage: stream-bench.sh [-k] FILE}
|
|
|
|
sid=$(./ui-sandbox.sh api /sessions | python3 -c 'import json,sys; print(json.load(sys.stdin)[0]["id"])')
|
|
|
|
if [ -z "$keep" ]; then
|
|
adb shell am force-stop com.example.aiapp
|
|
adb shell am start -n com.example.aiapp/.MainActivity >/dev/null
|
|
sleep 5
|
|
ui-trace record -d 3000 --do 'tap 500 545' -o /tmp/bench-open.txt >/dev/null 2>&1
|
|
sleep 3
|
|
fi
|
|
|
|
# Pinned to the newest end before anything is sent. The transcript never
|
|
# moves under a reader who is further back (see TranscriptList), so a reply
|
|
# streaming into a session parked at an older row arrives entirely
|
|
# off-screen: nothing recomposes, nothing draws, and the report comes back
|
|
# with two recompositions in it and no streaming counters at all. That
|
|
# reads exactly like a build where the work vanished. The control is the
|
|
# app's own "Jump to latest", which is only there while the newest message
|
|
# is off screen -- so when it is absent the list is already where it needs
|
|
# to be.
|
|
jump=$(ui-trace record -d 1200 -o /tmp/bench-jump.txt >/dev/null 2>&1
|
|
ui-trace show /tmp/bench-jump.txt -m 'Jump to latest' --field box |
|
|
grep -o '[0-9]*,[0-9]*\.\.[0-9]*,[0-9]*' | tail -1)
|
|
if [ -n "$jump" ]; then
|
|
x=$(echo "$jump" | awk -F'[,.]' '{print int(($1 + $4) / 2)}')
|
|
y=$(echo "$jump" | awk -F'[,.]' '{print int(($2 + $5) / 2)}')
|
|
ui-trace record -d 1500 --do "tap $x $y" -o /tmp/bench-tolatest.txt >/dev/null 2>&1
|
|
fi
|
|
|
|
# The first tap resets the report's window; see transcript-bench.sh.
|
|
ui-trace record -d 2000 --do 'tap 723 205' -o /tmp/bench-reset.txt >/dev/null 2>&1
|
|
adb logcat -c
|
|
|
|
./ui-sandbox.sh send "$sid" "@$file" >/dev/null
|
|
|
|
# Until the reply has finished, measured by the transcript rather than by
|
|
# the status. A session is idle at both ends of a turn, and polling for
|
|
# "idle" answers on the first poll -- before the turn has even started --
|
|
# so the report then covers the moment between the send and the first
|
|
# delta, and prints a window with nothing in it. The event count only
|
|
# grows, so "it stopped growing" is the one signal that cannot be true
|
|
# before the work begins.
|
|
events() {
|
|
./ui-sandbox.sh api "/sessions/$sid/transcript?limit=1" |
|
|
python3 -c 'import json,sys; d=json.load(sys.stdin); print(d[-1]["seq"] if d else 0)'
|
|
}
|
|
last=""
|
|
still=0
|
|
i=0
|
|
while [ "$i" -lt 180 ]; do
|
|
now=$(events)
|
|
if [ "$now" = "$last" ]; then
|
|
still=$((still + 1))
|
|
[ "$still" -ge 2 ] && break
|
|
else
|
|
still=0
|
|
fi
|
|
last=$now
|
|
sleep 2
|
|
i=$((i + 1))
|
|
done
|
|
sleep 1
|
|
|
|
ui-trace record -d 2000 --do 'tap 723 205' -o /tmp/bench-report.txt >/dev/null 2>&1
|
|
sleep 1
|
|
adb logcat -d -s ai-app:I | sed -n '/ai-app render report/,$p' | sed 's/^.*ai-app : //'
|