Files

190 lines
7.5 KiB
Markdown
Raw Permalink Normal View History

# 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 <crate>` to modify, `./lock.sh <crate>` 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
```