From bdc62b6442cd348c5e9d1d33edfa0d172131aa0f Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Mon, 31 Aug 2026 02:46:45 -0400 Subject: [PATCH] Time the draw phase, since counting it stopped being informative The counters did their job and then ran out: 4,090 rows drawn against 94,815 skipped, the tallest drawn block down from 36,982px to 1,765px, and drawing still took 30.9ms at the median. Almost nothing is being recorded and recording is still the expensive phase, so the next question is not "how much" but "where" -- and there are only two answers left. Either the little that is drawn is somehow costly, or the time is not inside the transcript at all and every count above is beside the point. So the draw is timed at three levels that nest: the whole transcript, one row, one block. If the transcript's own figure is most of the frame's draw, the cost is ours and the rows and blocks say which. If it is a fraction of it, the frame is being spent somewhere this has not been looking. Co-Authored-By: Claude Opus 5 --- .../kotlin/com/example/aiapp/MessageBlocks.kt | 2 ++ .../com/example/aiapp/TranscriptScroll.kt | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/MessageBlocks.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/MessageBlocks.kt index 4bc29a8..b1ea9d7 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/MessageBlocks.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/MessageBlocks.kt @@ -101,7 +101,9 @@ fun BlockedMarkdown(text: String, replies: ParsedReplies, modifier: Modifier = M "tallest drawn block px", offsets.height(index).toLong(), ) + val started = System.nanoTime() drawContent() + DebugStats.record("draw: one block", System.nanoTime() - started) } else { DebugStats.count("block skipped") } diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptScroll.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptScroll.kt index ce8f081..76fcf92 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptScroll.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/TranscriptScroll.kt @@ -366,6 +366,11 @@ fun TranscriptColumn( // One gesture detector for the whole list; see [TranscriptScroll.tappedHigh]. On the // initial pass and consuming nothing, so every control inside still gets the gesture // exactly as it would have. + .drawWithContent { + val started = System.nanoTime() + drawContent() + DebugStats.record("draw: the whole transcript", System.nanoTime() - started) + } .pointerInput(Unit) { awaitEachGesture { state.touched( @@ -387,7 +392,18 @@ fun TranscriptColumn( // Composed and measured whether or not it is drawn; see // [TranscriptScroll.onScreen]. The check reads the scroll position from // the draw phase, so moving the list invalidates drawing and nothing else. - .drawWithContent { if (state.onScreen(item.startSeq)) drawContent() } + // Timed as well as counted. Counting said what was skipped, and the + // answer stopped being useful the moment almost everything was: 4,090 rows + // drawn against 94,815 skipped, and drawing still took 30.9ms. A timer + // says which of the two remaining answers is true -- that the little being + // drawn is somehow expensive, or that the time is not in the transcript at + // all and every count here is beside the point. + .drawWithContent { + if (!state.onScreen(item.startSeq)) return@drawWithContent + val started = System.nanoTime() + drawContent() + DebugStats.record("draw: one row", System.nanoTime() - started) + } ) { // So a block of a long reply can ask the same question the row just answered, // about its own part of it; see [RowWindow].