Files
hcie-rust-v3.05/.kilo/plans/1784909963922-criterion-move-and-optimize.md
T
Your Name 8563a44211
mandatory-regression-gate / deterministic-tests (push) Has been cancelled
mandatory-regression-gate / protected-performance-path (push) Has been cancelled
Add SVG report for typical stroke mask pooling at warm 10th stroke
2026-07-24 23:43:42 +03:00

190 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```