# Plan: Relocate Criterion Results + Optimize to 2× gold_standard ## Context Criterion benchmarks exist under `target/criterion/` with gold-standard baselines already saved. The `criterion-test` branch introduced benchmarks; the current `performance-optimization` branch added `bench = false` and reverted `blend_mode`. **Current gold_standard timings (mean):** | Benchmark | ns (mean) | ~seconds | |---|---|---| | `below_cache_reuse/first_stroke_cache_build` | 807,729,115 | 0.81 s | | `below_cache_reuse/second_stroke_cache_reuse` | 14,573,274 | 0.015 s | | `composite_scratch_pooling/cold_composite` | 1,048,196,885 | 1.05 s | | `composite_scratch_pooling/warm_composite` | 10,073,839 | 0.010 s | | `effects_skip/no_effects_composite` | 14,340,411 | 0.014 s | | `effects_skip/with_one_dropshadow` | 14,780,582 | 0.015 s | | `stroke_mask_pooling/cold_first_stroke` | 46,427,571 | 0.046 s | | `stroke_mask_pooling/warm_10th_stroke` | 16,749,902 | 0.017 s | Dominant costs: **first_stroke_cache_build** (0.81 s) and **cold_composite** (1.05 s). **Target:** 2× faster on cold/first-stroke paths; zero regressions on warm paths. --- ## Phase 1 — Relocate `target/criterion` to project root ### 1.1: Move the results directory ```bash mv target/criterion ./criterion ``` ### 1.2: Update `.gitignore` Add `!/criterion` exception. The current rule `/target/` won't affect root-level `criterion/` but `/target/` plus `**/target/` should be reviewed. Actually `/target/` only matches the root `target/` dir, and `**/target/` matches nested ones, so `criterion/` at root is fine. No `.gitignore` change needed. ### 1.3: Configure criterion output directory Criterion 0.5 uses `CRITERION_HOME` env var (defaults to `target/criterion`). After move, either: **Option A (preferred):** Set `CRITERION_HOME` via `.cargo/config.toml`: ```toml [env] CRITERION_HOME = "criterion" ``` **Option B:** Export before benching: ```bash CRITERION_HOME=criterion cargo bench -p hcie-engine-api ``` ### 1.4: Update `criterion.md` Replace `./target/criterion/report/index.html` → `./criterion/report/index.html`. ### 1.5: Update `notlar.txt` Update the bench command section to reflect `CRITERION_HOME=criterion` usage. --- ## Phase 2 — Fix benchmark correctness ### 2.1: Remove `bench = false` from `hcie-engine-api/Cargo.toml` Line 37: `bench = false` must be removed for `[[bench]]` targets to work correctly. This was a workaround from commit `ee6ad2d`. ### 2.2: Verify `effects_skip.rs` blend_mode Current `"Normal".to_string()` matches `LayerStyle::DropShadow { blend_mode: String }` — correct. The `criterion-test` branch had `hcie_engine_api::BlendMode::Normal` which is a type mismatch. No change needed. --- ## Phase 3 — Optimization loop targeting 2× on cold paths **Prerequisite:** Engine crates are locked (`chmod 444`). Use `./unlock.sh ` to modify, `./lock.sh ` after. ### 3.0: Save current state as gold_standard baseline ```bash cargo bench -p hcie-engine-api -- --save-baseline gold_standard ``` ### 3.1: Optimize `below_cache` construction (first_stroke_cache_build: 0.81 s → ≤0.40 s) **Unlock:** `./unlock.sh hcie-engine-api` **File:** `hcie-engine-api/src/stroke_cache.rs:513-608` → `rebuild_below_cache_if_needed()` The function calls `tiled::composite_tiled_into()` for the full canvas (3840×2160). Optimization candidates: 1. **Parallelize the below-cache tile build loop** (line 547-561). Currently iterates layers sequentially to build tiles. Use `par_iter` for tile construction. 2. **Skip fully invisible layers in below-cache.** Check `tl.visible` before compositing. 3. **Pre-fill with topmost opaque Normal-blend layer.** Walk bottom→top, find the first fully opaque Normal-blend layer, fill everything below it at once. 4. **Reduce zero-fill cost.** The 33 MB `cache.fill(0)` at line 564 is sequential. Consider `unsafe { std::ptr::write_bytes }` for SIMD-accelerated zeroing. **Lock:** `./lock.sh hcie-engine-api` ### 3.2: Optimize cold composite (cold_composite: 1.05 s → ≤0.52 s) **Unlock:** `./unlock.sh hcie-engine-api` **File:** `hcie-engine-api/src/partial_composite.rs:90-228` → `render_composite_region()` cold path (lines 194-222) The cold path zeroes the scratch buffer then composites all layers. Optimization candidates: 1. **Parallel scratch buffer zeroing.** Replace row-by-row `buf[start..end].fill(0)` with `par_chunks_exact_mut`. 2. **Merge zero-fill with first-layer composite.** Instead of fill(0) then composite, start the first layer's composite directly (it overwrites anyway if using Normal blend). 3. **Skip tile sync for unchanged layers.** In `apply_effects_and_sync_tiles()`, the `sync_dirty_tiles()` re-tiles every dirty layer even if pixels haven't changed since last sync. 4. **Cache the tile compositing results.** If the same layer stack was composited before, reuse intermediate results. **Lock:** `./lock.sh hcie-engine-api` ### 3.3: Optimize tiled compositing inner loop **Unlock:** `./unlock.sh hcie-composite` **File:** `hcie-composite/src/tiled.rs` The `composite_tiled_into()` function handles small-region vs full-canvas compositing. The AGENTS.md note says small regions use sequential, large regions use `par_chunks_exact_mut`. Verify the full-canvas path (below-cache build and cold composite) actually uses parallel compositing. Optimization candidates: 1. **Ensure `par_chunks_exact_mut` is used for full-canvas compositing** in tiled.rs. 2. **Reduce per-tile overhead.** Check if tile lookups, bounds checks, or branching in the hot loop can be eliminated. **Lock:** `./lock.sh hcie-composite` ### 3.4: Validate after each optimization After EACH change: ```bash # Pixel-perfect correctness (MUST pass) cargo test -p hcie-engine-api --test visual_regression # Regression check vs gold_standard (MUST show improvement, no regressions) cargo bench -p hcie-engine-api -- --baseline gold_standard # Performance regression test cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture ``` ### 3.5: Re-save gold_standard after achieving target ```bash cargo bench -p hcie-engine-api -- --save-baseline gold_standard ``` --- ## Phase 4 — Re-lock all engine crates ```bash ./lock.sh hcie-engine-api ./lock.sh hcie-composite ``` --- ## Files Modified | File | Change | |---|---| | `target/criterion/` → `./criterion/` | Directory move | | `.cargo/config.toml` | Add `CRITERION_HOME = "criterion"` | | `criterion.md` | Update output path | | `notlar.txt` | Update bench instructions | | `hcie-engine-api/Cargo.toml` | Remove `bench = false` | | `hcie-engine-api/src/stroke_cache.rs` | below_cache optimization | | `hcie-engine-api/src/partial_composite.rs` | Cold composite optimization | | `hcie-composite/src/tiled.rs` | Tiled compositing optimization | --- ## Risks | Risk | Mitigation | |---|---| | `CRITERION_HOME` doesn't work with criterion 0.5 | Fallback: use `--output-dir criterion` flag or `.cargo/config.toml` `[env]` section | | Engine optimizations break pixel-perfect rendering | Run `visual_regression` test after every change | | Changes to hot path break AGENTS.md-protected mechanisms | Verify all protected mechanisms still pass; no changes to pooling logic correctness | | 2× target not achievable with these optimizations | Profile further; consider SIMD, parallelism in deeper layers | --- ## Validation Commands ```bash cargo bench -p hcie-engine-api -- --baseline gold_standard cargo test -p hcie-engine-api --test visual_regression cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture cargo bench -p hcie-engine-api --no-run ```