diff --git a/app/src/main/java/de/softwareapp_hb/filemanager/ui/browser/BrowserViewModel.kt b/app/src/main/java/de/softwareapp_hb/filemanager/ui/browser/BrowserViewModel.kt index fa5432f..dc44a44 100644 --- a/app/src/main/java/de/softwareapp_hb/filemanager/ui/browser/BrowserViewModel.kt +++ b/app/src/main/java/de/softwareapp_hb/filemanager/ui/browser/BrowserViewModel.kt @@ -329,12 +329,12 @@ class BrowserViewModel( * Derives what is shown from [listing]: hidden files filtered out, then sorted. * * A big folder is sorted on [io] instead of on the caller's thread. `File.listFiles` and the entry - * data are already read off the main thread, but the *sort* was not: the default order is natural - * order, which parses the digits in every name on every comparison, so twenty thousand rows means - * a few hundred thousand string comparisons in the middle of a tap. Measured on the phone at that - * size, one iteration out of five took 598 frames against a median of 184 — the kind of hitch this - * moves off the frame. Small folders stay on the caller's thread, which keeps the code (and the - * tests) honest for the case that happens all the time. + * data are already read off the main thread, but the *sort* was not, and with natural order the digits + * in every name are parsed on every comparison. Measured on the JVM, twenty thousand entries sort in + * **3–23 ms** — cheap enough that this is not the reason the 20 000-file benchmark had one iteration at + * 598 frames against a median of 184 (that outlier is still unexplained; see docs/PERFORMANCE.md). It + * is moved anyway: work the frame has no reason to wait for belongs off the frame, and the threshold + * leaves the common, small folder on the simple synchronous path. */ private fun recompute() { val current = _state.value diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index 02ef7f5..fa9f753 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -143,19 +143,22 @@ three seconds' worth of frames; one iteration of five took three times that, so is not merely slow but occasionally stalls — worth watching rather than explaining away, and the reason the 20 000 point is the one a release should be checked against. -Reading the listing path afterwards found a candidate that explains a hitch of exactly that shape: the -entries were read off the main thread but **sorted on it**. `AllFilesSource.list` already hops to -`Dispatchers.IO`, but `BrowserViewModel.recompute` filtered and sorted whatever `list` returned on the -caller's thread — and the default order is natural order, which parses the digits in every name on every -comparison. At 20 000 rows that is a few hundred thousand string comparisons inside a tap, plus a fresh -20 000-element list, which is the sort of work that is usually fine and occasionally not. +**What the outlier is not.** The obvious suspect was the sort: `AllFilesSource.list` already hops to +`Dispatchers.IO`, but `BrowserViewModel.recompute` filtered and sorted the result on the caller's thread, +and the default order is natural order, which parses the digits out of every name on every comparison. +Measured on the JVM, though, sorting **20 000 entries takes 3–23 ms** — three orders of magnitude away from +a 598-frame iteration (about ten seconds). So the sort does not explain the outlier, and saying it did would +have been a story that fitted the code rather than the measurement. -`recompute` now sorts off the main thread above 500 rows (`SORT_OFF_MAIN_THRESHOLD`) and keeps the simple -synchronous path below it, so small folders — the common case, and the one the tests exercise — behave -exactly as before. The dispatcher is injected, so the large path is covered by a test that lists 600 files -and asserts they come out in natural order. **It is not re-measured**: the 20 000-file fixture takes half an -hour to build through MIUI's storage layer, so the 598 outlier stands as the motivation and the next -person with that fixture should confirm the max has come down. +Where the time actually goes is **per entry**: `readdir` plus a `stat` for size and date, which is what the +ladder shows — going from 1 000 to 5 000 files added 27 frames (about 0.45 s), and 5 000 to 20 000 added 143 +(about 2.4 s), i.e. ~0.1 ms per file with nothing superlinear in it. Twenty thousand files therefore costs +roughly two seconds of listing before a pixel is drawn, and the 598-frame iteration is most likely a GC or a +scheduler hiccup on top of that — unidentified, and worth a perfetto trace rather than another theory. + +`recompute` still sorts large folders off the main thread, as a precaution rather than a fix: 3–23 ms is +cheap in absolute terms, but it is work the frame has no reason to wait for, and the threshold keeps small +folders on the simple synchronous path. Building that fixture is the expensive part, not measuring it: 20 000 files took about thirty minutes through MIUI's storage layer (`: >` in a loop), against roughly four minutes of benchmark on top. Worth