Take the two main-thread disk reads off the startup path, and report what that bought

Both costs StrictMode found on a cold start are now off the main thread. scratchRoot is a Lazy<File>, so Context.getCacheDir - which checks and creates the private cache directory, measured at 492 ms - runs when a preview first copies something instead of while the view model is being created. The free-space probes moved into fillRootFreeSpace and fillFreeSpace, which run on Dispatchers.IO after the rows are already on screen and update the state when the filesystem answers; nothing waits for them, and fillFreeSpace checks the path is still the one on screen so a fast double-tap cannot leave a folder showing another one's number.

Measured after: StrictMode no longer reports either violation, and the only ones left are MIUI's own font hook - FontSettings.loadFontSetting, TypefaceUtils.loadFontSettings, InterceptorProxy.create via MainActivity.onCreate, in 50-78 ms pairs. The debug cold start went from a 1814 ms median to 1803 ms and the benchmark coldStart from 519.5/531.6 ms to 485.6/568.9/600.0 ms. That is no improvement, and PERFORMANCE.md says so in those words: both violations were first-call costs (cache directory creation once per install, a cold StatFs page cache), the timed runs are warm, and the device's run-to-run spread of 115 ms is wider than anything the change could have bought. What it does change is the launch right after an install, which is the benchmark's first iteration: 648 ms max fresh against 550 ms warm.
This commit is contained in:
2026-10-17 16:11:28 +02:00
parent cbb36abfce
commit 14615f9842
4 changed files with 91 additions and 12 deletions
+2 -2
View File
@@ -2,7 +2,7 @@
Generated from the source by `tools/make-code-map.py` - do not edit by hand. Regenerate it after a change (`python3 tools/make-code-map.py`); `--check` exits non-zero when this file is stale.
77 source files, 12741 lines - 33 test files, 386 tests. The behaviour index at the end lists every test name, and in this project a test name is a sentence about what the app does, so it doubles as the specification.
77 source files, 12793 lines - 33 test files, 386 tests. The behaviour index at the end lists every test name, and in this project a test name is a sentence about what the app does, so it doubles as the specification.
Start at `AGENTS.md`. What is done, and why: `docs/ROADMAP.md` and `CHANGELOG.md`. What was verified by hand rather than by a test: `docs/EDGE_CASE_TESTING.md`.
@@ -101,7 +101,7 @@ The browser screen: view model, state, list and grid, dialogs, selection bar
| `BrowserScreen.kt` | 591 | BrowserScreen() | - |
| `BrowserTopBar.kt` | 238 | The browser's app bar: title, up, the favourite star, and the search / sort / overflow actions. BrowserTopBar(), SEARCH_FIELD_WIDTH | `BrowserTopBarTest` (4) |
| `BrowserUiState.kt` | 311 | One immutable description of everything the browser shows. ViewMode, SearchScope, Clipboard, UiMessage, ConflictPrompt, RootItem, Bookmark, DetailFocus, Undo, UndoMove, Overlay, BrowserUiState | - |
| `BrowserViewModel.kt` | 2100 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (89) |
| `BrowserViewModel.kt` | 2152 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (89) |
| `DetailsSheet.kt` | 109 | Name, location, size, type and modified date for one entry. DetailsSheet(), DetailLines(), DetailLine() | - |
| `EntryIntents.kt` | 58 | Wraps an entry's path in a content:// URI another app may read. contentUriFor(), viewIntent(), shareIntent() | - |
| `IncomingBar.kt` | 60 | The bar shown while another app is waiting on this one. IncomingBar() | - |
+29 -4
View File
@@ -46,10 +46,35 @@ The same debug build now logs StrictMode violations, and the first cold start pr
| 477 ms | `FileManagerTheme` → `typography = FileManagerTypography` | no — the platform loading its own typefaces on first composition |
| 54 ms × 2 | `miui.util.font.FontSettings` via `MainActivity.onCreate` | no — the OEM's font hook doing `File.exists` while the activity is created |
That is **~1.5 s of main-thread disk I/O inside a 1.8 s start**, of which **~0.9 s is ours**. Both of
ours are mechanical to move and neither is fixed yet: the cache directory is only needed when a preview
copies something, and the free-space line on the roots screen can arrive a frame later. They are the
first items of the speed work.
That is **~1.5 s of main-thread disk I/O inside a 1.8 s start**, of which **~0.9 s was ours**. Both of
ours were mechanical to move — the cache directory is only needed when a preview copies something, and the
free-space line can arrive a frame later — and both are fixed now; what they bought, measured, is below.
### After the fix: what moved, and what did not
Both are now fixed — `scratchRoot` is a `Lazy<File>` resolved on first use, and the free-space probes run
in `Dispatchers.IO` and update the state when they return (`fillRootFreeSpace`, `fillFreeSpace`). Measured
afterwards:
| what | before | after |
|---|---|---|
| StrictMode at start, ours | 492 ms `cacheDir`, 397 ms `StatFs` | **gone** |
| StrictMode at start, not ours | 477 ms typefaces, 54 ms × 2 MIUI font hook | 50–78 ms in pairs, all from `miui.util.font.FontSettings.loadFontSetting` / `TypefaceUtils.loadFontSettings` / `contentcatcher.InterceptorProxy.create` via `MainActivity.onCreate` |
| debug cold start, 5 runs | median 1 814 ms | median 1 803 ms (1 697 · 1 752 · 1 803 · 1 823 · 2 507) |
| `coldStart` on the benchmark build | 519.5 / 531.6 ms | 485.6 / 568.9 / 600.0 ms |
**The numbers did not improve, and that is worth stating rather than dressing up.** The two violations were
*first-call* costs: creating the cache directory happens once per install, and `StatFs` is slow when its
page cache is cold. Neither is paid on a warm device, which is what all five timed runs are — and the
run-to-run spread on this phone (±115 ms between the three benchmark runs above) is larger than anything
the change could have bought. What the fix does buy is that the main thread no longer does this work at
all, which is exactly the case the benchmark's *first* iteration shows: its max is 648 ms after a fresh
install, against 550 ms on a warm one.
The honest conclusion is that the remaining 1.8 s of a debug start is not made of these two calls. It is
R8-less code, Compose's first composition, the typefaces, and MIUI's font hook — and the release-like
build already pays 0.5 s, not 1.8 s. Making a *number* out of any further work needs either a quieter
device or many more runs; making the *app* faster needs a baseline profile (next section).
## Measured on demand: the `:benchmark` module