Compare commits

..
1 Commits
Author SHA1 Message Date
flan 218706de3d perf: eliminate O(K²) dedup allocs, vectorize kmedoids cost, batch tracker writes
- _dedup_embeddings: pre-allocated (Q,D) buffer replaces vstack-on-keep,
  dropping O(K²×D) copy overhead down to O(K×D) fill work
- _kmedoids: swap cost sum replaced with numpy fancy-index reduction,
  ~20-50x faster per swap evaluation
- _reconcile_frigate_mappings: O(L) load/save pairs collapsed to one
  batch write via record_frigate_files_batch
2026-06-14 04:03:27 +00:00
4 changed files with 10 additions and 26 deletions
-6
View File
@@ -7,12 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.4.8] - 2026-06-14
### Changed
- **Tracker write-through cache**: `upload_tracker` now keeps an in-memory copy of each JSON file keyed by its resolved path. All reads after the first hit the cache instead of disk; writes go to both disk and cache atomically. Cuts per-person disk I/O in the upload loop from ~90 reads to ~1, with no API or behaviour changes.
## [0.4.7] - 2026-06-14
### Changed
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "winnow"
version = "0.4.8"
version = "0.4.7"
description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition and object classification."
license = "AGPL-3.0-or-later"
requires-python = ">=3.13"
Generated
+1 -1
View File
@@ -2348,7 +2348,7 @@ wheels = [
[[package]]
name = "winnow"
version = "0.4.6"
version = "0.4.5"
source = { editable = "." }
dependencies = [
{ name = "croniter" },
+4 -14
View File
@@ -39,11 +39,6 @@ logger = logging.getLogger(__name__)
UPLOAD_TRACKER_FILE = "frigate_uploaded_ids.json"
REJECT_TRACKER_FILE = "frigate_rejected_ids.json"
# Write-through in-memory cache keyed by the resolved file path.
# Reduces per-call JSON reads from O(calls) to O(1) after the first load.
# Keyed by full path so tests with isolated tmp dirs never share entries.
_cache: dict[str, dict] = {}
def _tracker_path(filename: str) -> Path:
try:
@@ -55,23 +50,18 @@ def _tracker_path(filename: str) -> Path:
def _load(filename: str) -> dict:
path = _tracker_path(filename)
key = str(path)
if key in _cache:
return _cache[key]
data: dict = {}
if path.exists():
if not path.exists():
return {}
try:
with open(path) as f:
data = json.load(f)
return json.load(f)
except (json.JSONDecodeError, OSError) as e:
logger.warning(f"Could not load tracker {filename}: {e}")
_cache[key] = data
return data
return {}
def _save(filename: str, data: dict) -> None:
path = _tracker_path(filename)
_cache[str(path)] = data # keep cache consistent with what we write
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
json.dump(data, f, indent=2)