1+{
2+ "published": [
3+ {
4+ "base": "03ead6ffc01b2ea0adf6f2c87d59e86b9615566d",
5+ "head": "03ead6ffc01b2ea0adf6f2c87d59e86b9615566d",
6+ "cob": ""
7+ },
8+ {
9+ "base": "03ead6ffc01b2ea0adf6f2c87d59e86b9615566d",
10+ "head": "03ead6ffc01b2ea0adf6f2c87d59e86b9615566d",
11+ "cob": "c4b135bf3bcf25661f3d7e8207e4156221874760"
12+ }
13+ ]
14+}1 {"type":"config","name":"Optimize rendy OBJ mesh loading","metricName":"total_us","metricUnit":"µs","bestDirection":"lower"}
2+{"run":1,"commit":"03ead6f","metric":168051,"metrics":{"bytes":978234,"grid":96,"iterations":24,"objects":24},"status":"keep","description":"Baseline for generated-grid OBJ loading benchmark.","timestamp":1776686112516,"segment":0,"confidence":null,"iterationTokens":80,"asi":{"hypothesis":"Establish a stable baseline for end-to-end OBJ parse+conversion time before changing loader internals.","workload":"generated 96x96 grid OBJ, 24 loads per sample, median of 5 samples","observation":"Benchmark is stable enough to target allocation and iterator overhead inside mesh/src/format/obj.rs."}}
3+{"run":2,"commit":"03ead6f","metric":169721,"metrics":{"bytes":978234,"grid":96,"iterations":24,"objects":24},"status":"discard","description":"Fuse triangle expansion into a single pass with preallocated output vectors.","timestamp":1776686156595,"segment":0,"confidence":null,"iterationTokens":2267,"asi":{"hypothesis":"Eliminating the temporary index list and three iterator/collect passes should reduce allocations and traversal overhead.","rollback_reason":"Benchmark regressed slightly versus baseline; extra match logic inside the fused loop likely outweighed the saved temporary vector on this workload.","next_action_hint":"Try retaining the simpler multi-pass structure but preallocate exact capacities and reduce repeated iterator machinery more surgically.","learning":"The parser cost may dominate enough that micro-optimizing traversal shape alone is not a guaranteed win."}}
4+{"run":3,"commit":"03ead6f","metric":168392,"metrics":{"bytes":978234,"grid":96,"iterations":24,"objects":24},"status":"discard","description":"Preallocate object/index storage while keeping the original three-pass vertex extraction structure.","timestamp":1776686218166,"segment":0,"confidence":null,"iterationTokens":440,"asi":{"hypothesis":"Exact-capacity reservation may trim allocator overhead without changing the loader's dataflow.","rollback_reason":"A confirmation rerun landed above baseline, so the tiny apparent improvement was noise-level and not trustworthy.","next_action_hint":"Look for changes that cut larger costs, or improve benchmark instrumentation to isolate parser versus conversion time.","learning":"Capacity reservation alone does not produce a meaningful end-to-end win on this workload.","note":"One immediate confirmation rerun after an initial near-tie also failed to show a durable improvement."}}
5
6 ## Metrics
7 - **Primary**: `total_us` (µs, lower is better) — median end-to-end load time across repeated benchmark runs
8-- **Secondary**: `bytes`, `grid`, `iterations`, `objects` — workload sanity checks
8+- **Secondary**: `parse_us`, `convert_us`, `bytes`, `grid`, `iterations`, `objects` — parser/conversion split and workload sanity checks
9
10 ## How to Run
11 `./autoresearch.sh` — outputs structured `METRIC ...` lines.
27 - Prefer wins from reducing allocations/copies/iterator overhead in the loader
28
29 ## What's Been Tried
30-- Baseline pending.
30+- Baseline: `total_us=168051` on the generated 96x96 grid workload.
31+- Fusing triangle expansion and vertex extraction into one loop regressed slightly.
32+- Capacity reservation alone produced only noise-level movement.
33+- Added parser/conversion split metrics to learn whether future wins should target `wavefront_obj` overhead or Rendy's conversion layer.
1 use rendy_mesh::obj::load_from_obj;
2 use std::{hint::black_box, time::Instant};
3+use wavefront_obj::obj;
4
5 fn build_grid_obj(grid: usize) -> Vec<u8> {
6 let vertex_cols = grid + 1;
61 let obj = build_grid_obj(grid);
62 let mut total_objects = 0usize;
63
64+ let parse_started = Instant::now();
65+ for _ in 0..iters {
66+ let parsed = obj::parse(std::str::from_utf8(&obj).expect("generated obj should be utf8"))
67+ .expect("obj parse should succeed");
68+ black_box(parsed);
69+ }
70+ let parse_elapsed = parse_started.elapsed();
71+
72 let started = Instant::now();
73 for _ in 0..iters {
74 let meshes = load_from_obj(&obj).expect("obj load should succeed");
77 let elapsed = started.elapsed();
78
79 println!("BENCH elapsed_us={}", elapsed.as_micros());
80+ println!("BENCH parse_us={}", parse_elapsed.as_micros());
81+ println!("BENCH convert_us={}", elapsed.saturating_sub(parse_elapsed).as_micros());
82 println!("BENCH bytes={}", obj.len());
83 println!("BENCH grid={}", grid);
84 println!("BENCH iterations={}", iters);