190 lines
7.5 KiB
Markdown
190 lines
7.5 KiB
Markdown
# 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
|
||
```
|