Do not recount face coverage on every confirm, and count it without listing
Every click on the Identity screen's face grid — confirm, reject, split, rename, merge — redrew the whole screen, and the redraw recomputed the coverage line. That line lists every repair's outstanding images to count them: six scans of the images table with a correlated EXISTS over the 8 KB face rows, an ORDER BY the job's visiting order, a Target with its path per row, and a thumbnail-index query per image with faces. On the reference library (24k images, 19k faces) that was ~200 ms of the ~540 ms each click cost, spent computing a figure a confirm cannot change. `refresh` now takes what changed: `Changed::Identities` re-reads the rail and the grid and leaves the coverage line alone; `Changed::Library` — an open, a sweep ending or stopped, the face data deleted — re-reads it too. For the times it does run, `repairs::counts` counts instead of building and dropping the lists, and the thumbnail store is read once (`ThumbStore::held`) rather than probed once per image in the audit, the outstanding list and the proxy repair. `identity_bench` is the measurement: the reads a click performs and the batch writes, timed against a copy of a real catalog.
This commit is contained in:
+59
-3
@@ -732,12 +732,19 @@ fn faces_without_proxy(
|
||||
WHERE r.file_id IS NOT NULL AND {VISIBLE} AND {} = ?1",
|
||||
faces::embedder_sql("f.model_id"),
|
||||
))?;
|
||||
// The index once, not a probe per image with faces — see
|
||||
// `ThumbStore::held`. An unreadable index reads as empty, as `contains`
|
||||
// would have reported it.
|
||||
let held = store.held(dr_thumbs::ThumbSize::Large).unwrap_or_else(|e| {
|
||||
log::warn!("repairs: reading the thumbnail index: {e}");
|
||||
Default::default()
|
||||
});
|
||||
let rows = stmt
|
||||
.query_map([embedder], |r| {
|
||||
Ok((r.get::<_, i64>(0)?, r.get::<_, i64>(1)?))
|
||||
})?
|
||||
.filter_map(Result::ok)
|
||||
.filter(|(_, file_id)| !store.contains(*file_id as u64, dr_thumbs::ThumbSize::Large))
|
||||
.filter(|(_, file_id)| !held.contains(&(*file_id as u64)))
|
||||
.map(|(id, _)| id)
|
||||
.collect();
|
||||
Ok(rows)
|
||||
@@ -802,7 +809,14 @@ fn still_owed(
|
||||
}
|
||||
}
|
||||
|
||||
/// How many images each repair still lists, for the settings line.
|
||||
/// How many images each repair still lists, for the settings line and the
|
||||
/// coverage line.
|
||||
///
|
||||
/// Counted, not listed. [`listed`] builds a `Target` per image — its path,
|
||||
/// its size — and sorts the faces-first order the job visits them in, none of
|
||||
/// which a count reads; asked for six repairs on a 24,000-image library that
|
||||
/// was 350 ms of `source_ref` strings built to be dropped. A `COUNT(*)` over
|
||||
/// the same predicate is the same number in a tenth of the time.
|
||||
pub fn counts(
|
||||
catalog: &Catalog,
|
||||
store: &ThumbStore,
|
||||
@@ -810,10 +824,52 @@ pub fn counts(
|
||||
) -> Result<Vec<(&'static str, u64)>, dr_catalog::CatalogError> {
|
||||
repairs
|
||||
.iter()
|
||||
.map(|r| Ok((r.label, listed(catalog, store, r)?.len() as u64)))
|
||||
.map(|r| Ok((r.label, count(catalog, store, r)?)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// How many images one repair lists — the size of [`listed`]'s answer,
|
||||
/// without building it.
|
||||
fn count(
|
||||
catalog: &Catalog,
|
||||
store: &ThumbStore,
|
||||
repair: &Repair,
|
||||
) -> Result<u64, dr_catalog::CatalogError> {
|
||||
match &repair.needs {
|
||||
Needs::Sql(sql) => {
|
||||
let n: i64 = catalog.connection().query_row(
|
||||
&format!(
|
||||
"SELECT COUNT(*)
|
||||
FROM images i
|
||||
JOIN remote r ON r.image_id = i.id
|
||||
WHERE r.file_id IS NOT NULL AND {VISIBLE} AND ({sql})"
|
||||
),
|
||||
[],
|
||||
|r| r.get(0),
|
||||
)?;
|
||||
Ok(n as u64)
|
||||
}
|
||||
// The set is built from its own query and may name images `listed`
|
||||
// would not visit, so it is intersected with the same base rather
|
||||
// than trusted for its size.
|
||||
Needs::Set(f) => {
|
||||
let set = f(catalog, store)?;
|
||||
let mut stmt = catalog.connection().prepare(&format!(
|
||||
"SELECT i.id
|
||||
FROM images i
|
||||
JOIN remote r ON r.image_id = i.id
|
||||
WHERE r.file_id IS NOT NULL AND {VISIBLE}"
|
||||
))?;
|
||||
let n = stmt
|
||||
.query_map([], |r| r.get::<_, i64>(0))?
|
||||
.filter_map(Result::ok)
|
||||
.filter(|id| set.contains(id))
|
||||
.count();
|
||||
Ok(n as u64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One image on the work list, with the most any repair claiming it asks
|
||||
/// for.
|
||||
struct Planned {
|
||||
|
||||
Reference in New Issue
Block a user