diff options
| author | Fuwn <[email protected]> | 2026-02-18 11:41:58 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-18 11:42:11 -0800 |
| commit | dd690e6be3bba1f0d3aa6e853e50e23cf44f75ec (patch) | |
| tree | 146ec3964c02579f63465c88199fcc8270d0c4e1 /docs | |
| parent | Fix SwiftLint violations (diff) | |
| download | sora-testing-dd690e6be3bba1f0d3aa6e853e50e23cf44f75ec.tar.xz sora-testing-dd690e6be3bba1f0d3aa6e853e50e23cf44f75ec.zip | |
test: add baseline test harness and performance baseline report
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/perf/2026-02-18-baseline.md | 92 | ||||
| -rw-r--r-- | docs/plans/2026-02-18-performance-and-redundancy-optimisation.md | 323 |
2 files changed, 415 insertions, 0 deletions
diff --git a/docs/perf/2026-02-18-baseline.md b/docs/perf/2026-02-18-baseline.md new file mode 100644 index 0000000..9240ad6 --- /dev/null +++ b/docs/perf/2026-02-18-baseline.md @@ -0,0 +1,92 @@ +# 2026-02-18 Baseline Metrics + +## Scope +- Task 1 baseline for performance/redundancy plan. +- Added `SoraTests` harness and failing baseline assertions. + +## Commands Run + +### Project generation +```bash +just generate +``` +Result: success (`Sora.xcodeproj` regenerated with `SoraTests` target). + +### Baseline tests via app scheme +```bash +xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test +``` +Result: failed before executing assertions because the simulator failed to initialise the test bundle host in the app scheme. + +### Baseline tests via test scheme (expected fail) +```bash +xcodebuild -project Sora.xcodeproj -scheme SoraTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test +``` +Result: failed as expected baseline, with 4 failures in 3 tests. + +Failure highlights: +- `SettingsManagerSyncTests.testBatchedSyncPathGuardsUnchangedPayloadWrites` + - `XCTAssertGreaterThanOrEqual failed: ("0") is less than ("4")` +- `ViewDerivedDataTests.testGenericListViewDerivedCollectionsAreReferencedOncePerRenderPass` + - `filteredItems` references: `3 > 1` + - `sortedFilteredItems` references: `3 > 1` +- `ViewDerivedDataTests.testPostGridViewDerivedCollectionsAreReferencedOncePerRenderPass` + - `activePosts` references: `6 > 1` + +### Format + lint +```bash +just format && just lint +``` +Result: success. + +### Debug simulator build +```bash +just build_ios_simulator Debug +``` +Result: success. + +## Timing Snapshots + +### Test run (SoraTests scheme) +Command: +```bash +/usr/bin/time -p sh -c "xcodebuild -project Sora.xcodeproj -scheme SoraTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test > /tmp/sora_task1_test.log 2>&1" +``` +Timing: +- real: `22.36s` +- user: `3.81s` +- sys: `2.91s` + +### Format + lint +Command: +```bash +/usr/bin/time -p sh -c "just format && just lint" +``` +Timing: +- real: `0.51s` +- user: `0.54s` +- sys: `0.11s` + +### Simulator debug build +Command: +```bash +/usr/bin/time -p just build_ios_simulator Debug +``` +Timing: +- real: `4.04s` +- user: `2.73s` +- sys: `1.43s` + +## Hotspot Files (Baseline) +- `Sora/Data/Settings/SettingsManager.swift` + - `triggerSyncIfNeeded(for:)` performs 4 `NSUbiquitousKeyValueStore.default.set(encoded, ...)` writes with no `!= encoded` unchanged guard in that batched sync path. +- `Sora/Views/Generic/GenericListView.swift` + - Repeated derived-data references in render path: + - `filteredItems`: 3 references + - `sortedFilteredItems`: 3 references +- `Sora/Views/Post/Grid/PostGridView.swift` + - `activePosts` referenced 6 times in the view/render path. + +## Notes +- Existing warning observed during build/test: + - `Sora.xcodeproj`: Copy Bundle Resources includes `Sora/Resources/Info.generated.plist`. diff --git a/docs/plans/2026-02-18-performance-and-redundancy-optimisation.md b/docs/plans/2026-02-18-performance-and-redundancy-optimisation.md new file mode 100644 index 0000000..84ce4b3 --- /dev/null +++ b/docs/plans/2026-02-18-performance-and-redundancy-optimisation.md @@ -0,0 +1,323 @@ +# Sora Performance And Redundancy Optimisation Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Reduce CPU, allocation, and redundant serialisation work in hot paths while preserving behaviour. + +**Architecture:** Optimise by isolating expensive repeated work into cached derived state and shared helpers. Refactor settings sync and serialisation flows to encode and merge once per mutation, then fan out updates deterministically. Consolidate duplicated UI menu and grid logic into reusable components so runtime work and maintenance overhead both drop. + +**Tech Stack:** Swift 6, SwiftUI, Alamofire, WaterfallGrid, XcodeGen (`project.yml`), XCTest (new test target). + +--- + +### Task 1: Add Test Harness And Baseline Metrics + +**Files:** +- Create: `SoraTests/SettingsManagerSyncTests.swift` +- Create: `SoraTests/ViewDerivedDataTests.swift` +- Modify: `project.yml` +- Modify: `justfile` +- Create: `docs/perf/2026-02-18-baseline.md` + +**Step 1: Add an XCTest target** + +Update `project.yml` with a `SoraTests` unit-test target that depends on `Sora`. + +**Step 2: Generate project** + +Run: `just generate` +Expected: project regenerates with `SoraTests` target. + +**Step 3: Add baseline tests that currently fail** + +Add tests that assert: +- settings sync does not write unchanged payloads +- derived list/grid computations are invoked once per dependency change, not multiple times per render pass + +**Step 4: Run tests to verify failures** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test` +Expected: new tests fail, proving current redundant work. + +**Step 5: Record baseline metrics** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` + +Write results, hotspot files, and timing snapshots into `docs/perf/2026-02-18-baseline.md`. + +**Step 6: Commit checkpoint** + +Run: +- `jj git init` (only if repo is not already a jj repo) +- `jj commit -m "test: add performance baseline and failing regression tests"` + +### Task 2: Refactor Settings Serialisation To Single-Encode Paths + +**Files:** +- Modify: `Sora/Data/Settings/SettingsManager.swift` +- Create: `Sora/Data/Settings/SettingsCodec.swift` +- Test: `SoraTests/SettingsManagerSyncTests.swift` + +**Step 1: Write failing tests for duplicate encoding** + +Add tests asserting add/update bookmark and favourite flows perform one encoded payload build per mutation path. + +**Step 2: Run test to verify it fails** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test -only-testing:SoraTests/SettingsManagerSyncTests` +Expected: failure showing repeated encode/decode calls. + +**Step 3: Implement minimal refactor** + +Extract codec helpers in `SettingsCodec.swift`: +- shared `JSONEncoder` and `JSONDecoder` +- encode-once helper returning `(value, encodedData)` shape +- equality guard to skip unchanged writes + +Wire `addBookmark`, `addFavorite`, and setter sync paths to reuse one encoded payload. + +**Step 4: Run tests to verify pass** + +Run same command as Step 2. +Expected: passing tests. + +**Step 5: Regression checks** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` + +**Step 6: Commit checkpoint** + +Run: `jj commit -m "refactor: remove duplicate settings serialisation work"` + +### Task 3: Serialize iCloud Sync Work Through One Coordinator + +**Files:** +- Modify: `Sora/Data/Settings/SettingsManager.swift` +- Create: `Sora/Data/Settings/SettingsSyncCoordinator.swift` +- Test: `SoraTests/SettingsManagerSyncTests.swift` + +**Step 1: Write failing tests for sync coalescing** + +Add tests asserting batched key updates are coalesced and unchanged blobs are not re-written. + +**Step 2: Run test to verify it fails** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test -only-testing:SoraTests/SettingsManagerSyncTests` +Expected: failing assertions for duplicate sync work. + +**Step 3: Implement minimal coordinator** + +Create `SettingsSyncCoordinator` (actor or serial queue) that: +- merges per key once +- writes local/iCloud only if bytes changed +- updates in-memory caches from merged arrays without decode-then-redecode loops + +Replace detached per-key sync fan-out in `SettingsManager`. + +**Step 4: Run tests to verify pass** + +Run same command as Step 2. +Expected: pass. + +**Step 5: Full verification** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` +- `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test` + +**Step 6: Commit checkpoint** + +Run: `jj commit -m "refactor: coalesce settings sync and skip unchanged writes"` + +### Task 4: Optimise GenericListView Derived Data + +**Files:** +- Modify: `Sora/Views/Generic/GenericListView.swift` +- Create: `Sora/Views/Shared/FolderHierarchy.swift` +- Test: `SoraTests/ViewDerivedDataTests.swift` + +**Step 1: Write failing tests for repeated derived computations** + +Add tests for: +- filtered + sorted list computed once per dependency change +- folder hierarchy grouping reused + +**Step 2: Run test to verify it fails** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test -only-testing:SoraTests/ViewDerivedDataTests` +Expected: fail due repeated computation counters. + +**Step 3: Implement minimal changes** + +Move expensive derivations into memoised state updated on explicit dependency changes: +- `items`, `searchText`, `selectedCollectionOption`, `selectedProvider`, `sort`, `settings.folders` + +Introduce shared `FolderHierarchy` model to avoid repeated inline `reduce`. + +**Step 4: Run tests to verify pass** + +Run same command as Step 2. +Expected: pass. + +**Step 5: Verification and commit** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` + +Run: `jj commit -m "perf: memoize generic list derived data and folder grouping"` + +### Task 5: Optimise FavoritesView Mapping And Grid Cost + +**Files:** +- Modify: `Sora/Views/FavoritesView.swift` +- Create: `Sora/Views/Shared/ThumbnailGridView.swift` +- Test: `SoraTests/ViewDerivedDataTests.swift` + +**Step 1: Write failing tests for repeated sort/map/grid splitting** + +Add tests asserting favourites-to-post mapping and column distribution run once per dependency change. + +**Step 2: Run test to verify it fails** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test -only-testing:SoraTests/ViewDerivedDataTests` +Expected: fail on repeated work assertions. + +**Step 3: Implement minimal changes** + +- Precompute sorted favourites and mapped posts once +- Pass mapped posts into `favoriteGridContent` instead of remapping per cell +- Reuse `ThumbnailGridView` + cached column distributor for both Waterfall and alternative modes + +**Step 4: Run tests to verify pass** + +Run same command as Step 2. +Expected: pass. + +**Step 5: Verification and commit** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` + +Run: `jj commit -m "perf: eliminate repeated favorites sort map and grid recompute"` + +### Task 6: Consolidate Folder Menu Construction Across Views + +**Files:** +- Modify: `Sora/Views/FavoritesView.swift` +- Modify: `Sora/Views/Generic/GenericListView.swift` +- Modify: `Sora/Views/Post/Grid/PostGridView.swift` +- Modify: `Sora/Views/BookmarkMenuButtonView.swift` +- Modify: `Sora/Views/FavoriteMenuButtonView.swift` +- Create: `Sora/Views/Shared/FolderMenuView.swift` +- Test: `SoraTests/ViewDerivedDataTests.swift` + +**Step 1: Write failing tests for folder hierarchy consistency** + +Add tests that each consumer renders equivalent hierarchy for the same folder data. + +**Step 2: Run test to verify it fails** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test -only-testing:SoraTests/ViewDerivedDataTests` +Expected: fail due duplicated divergent logic. + +**Step 3: Implement shared menu component** + +Create `FolderMenuView` and use it in all five call sites. Keep behaviour identical for “Uncategorized”, top-level folders, and disabled states. + +**Step 4: Run tests to verify pass** + +Run same command as Step 2. +Expected: pass. + +**Step 5: Verification and commit** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` + +Run: `jj commit -m "refactor: share folder menu hierarchy across views"` + +### Task 7: Remove Search And Image Hot-Path Waste + +**Files:** +- Modify: `Sora/Views/SearchSuggestionsView.swift` +- Modify: `Sora/Views/Post/Details/PostDetailsImageView.swift` +- Modify: `Sora/Data/ImageCacheManager.swift` +- Test: `SoraTests/ViewDerivedDataTests.swift` + +**Step 1: Write failing tests for per-keystroke cache rebuild and redundant image fetch** + +Add tests for: +- suggestions cache built on `items` change, not every keystroke +- image copy/save path reuses cached image data and avoids synchronous file/network loads + +**Step 2: Run test to verify it fails** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test -only-testing:SoraTests/ViewDerivedDataTests` +Expected: fail. + +**Step 3: Implement minimal changes** + +- Persist preprocessed suggestion names between keystrokes +- Replace `NSData(contentsOf:)` path with async/cached data path +- Clear completed image preload cancellables and cap effective concurrent preloads + +**Step 4: Run tests to verify pass** + +Run same command as Step 2. +Expected: pass. + +**Step 5: Verification and commit** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` + +Run: `jj commit -m "perf: reduce suggestion and image handling hot-path overhead"` + +### Task 8: Cleanup And Final Verification + +**Files:** +- Modify: `Sora/Data/Booru/BooruManager.swift` +- Modify: `Sora/Views/Post/Grid/PostGridView.swift` +- Modify: `docs/perf/2026-02-18-baseline.md` +- Create: `docs/perf/2026-02-18-after.md` + +**Step 1: Remove confirmed dead code and unstable shuffle comparators** + +Remove unused state (`cachedSuggestions`) and dead parser-pool code if still unused. Replace comparator-based random ordering with deterministic shuffle generation on explicit trigger. + +**Step 2: Run full test suite** + +Run: `xcodebuild -project Sora.xcodeproj -scheme Sora -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test` +Expected: all tests pass. + +**Step 3: Run project verification** + +Run: +- `just format && just lint` +- `just build_ios_simulator Debug` + +Expected: all commands pass. + +**Step 4: Capture after-metrics** + +Record post-change timings, render responsiveness notes, and serialisation counts in `docs/perf/2026-02-18-after.md`. + +**Step 5: Commit checkpoint** + +Run: `jj commit -m "chore: finalise performance optimisation batch and metrics"` + +### Rollout Notes + +- Keep each task isolated and checkpointed with `jj`. +- Do not batch Tasks 2-8 into one commit. +- If any task regresses behaviour, abandon only that change with `jj abandon @` and redo from its failing test. +- Preserve current UI and behaviour, this is an optimisation pass, not a feature change. |