Experiment: d1dce6c
rad experiment show d1dce6cd0d0ef02ee20ed2d1032a8aa9f83b94ac
by lftherioslftherios@did:key:z6MkupQHdQky5atyAu9zF8dUu1EavUjckDgCdZLH8y2W8teE · Apr 20 11:58 2026
Add parser/conversion split metrics to the benchmark to reveal where OBJ load time is spent.
Measurements
MetricBaselineThe benchmark measurement of the unmodified codeCandidateThe code with the proposed optimization appliedCandidate/Parent ΔThe performance change from baseline to candidate
total_us (µs) primary
Optimize `rendy_mesh::obj::load_from_obj` for lower CPU time when loading a moderately large generated OBJ mesh. The hot path includes OBJ parsing plus conversion into `MeshBuilder` vertex streams.
168051.000 µs (n=1)168804.000 µs (n=1)+0.44% REGRESSED
bytes secondary978234.000 (n=1)978234.000 (n=1)0.00%
convert_us secondary0.000 (n=1)6148.000 (n=1)0.00%
grid secondary96.000 (n=1)96.000 (n=1)0.00%
iterations secondary24.000 (n=1)24.000 (n=1)0.00%
objects secondary24.000 (n=1)24.000 (n=1)0.00%
parse_us secondary0.000 (n=1)163471.000 (n=1)0.00%
Annotations
hypothesisExtra benchmark instrumentation will show whether future optimization work should focus on wavefront parsing or Rendy's conversion layer.
next_focusFavor ideas that reduce UTF-8/parse overhead, or consider API-level fast paths that bypass reparsing when structured OBJ data is already available.
observationOn this workload, parse time (~163 ms) dwarfs conversion time (~6 ms), so most end-to-end wins likely require attacking parse overhead or changing the accepted input path.
reason_to_keepThis keeps the benchmark more informative for subsequent experiments even though the measured primary metric itself did not improve.
Base CommitThe starting commit before the optimization was applied03ead6ffc01b2ea0adf6f2c87d59e86b9615566d
Candidate CommitThe code with the proposed optimization applied6d8f169292ca23896b2321673d70b07132bce4d1
Bench script32c236e8e542
bench_cmdbash autoresearch.sh
Schemav5
Diff
+ .community-computer/pi-cc.json
@@ -0,0 +1,3 @@
1+{ 2+ "metric_description": "Optimize `rendy_mesh::obj::load_from_obj` for lower CPU time when loading a moderately large generated OBJ mesh. The hot path includes OBJ parsing plus conversion into `MeshBuilder` vertex streams." 3+}
+ .community-computer/published.json
@@ -0,0 +1,14 @@
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+}
~ autoresearch.jsonl
@@ -1 +1,4 @@
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."}}
~ autoresearch.md
@@ -5,7 +5,7 @@ Optimize `rendy_mesh::obj::load_from_obj` for lower CPU time when loading a mode
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,4 +27,7 @@ Optimize `rendy_mesh::obj::load_from_obj` for lower CPU time when loading a mode
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.
~ autoresearch.sh
@@ -15,10 +15,14 @@ bytes=""
15 grid="" 16 iters="" 17 objects="" 18+parse_us="" 19+convert_us="" 20 21 for _ in 1 2 3 4 5; do 22 output="$($bin --grid 96 --iters 24)" 23 elapsed=$(printf '%s\n' "$output" | awk -F= '/^BENCH elapsed_us=/{print $2}') 24+ parse_us=$(printf '%s\n' "$output" | awk -F= '/^BENCH parse_us=/{print $2}') 25+ convert_us=$(printf '%s\n' "$output" | awk -F= '/^BENCH convert_us=/{print $2}') 26 bytes=$(printf '%s\n' "$output" | awk -F= '/^BENCH bytes=/{print $2}') 27 grid=$(printf '%s\n' "$output" | awk -F= '/^BENCH grid=/{print $2}') 28 iters=$(printf '%s\n' "$output" | awk -F= '/^BENCH iterations=/{print $2}')
@@ -30,6 +34,8 @@ IFS=$'\n' sorted=($(printf '%s\n' "${runs[@]}" | sort -n))
34 median="${sorted[2]}" 35 36 echo "METRIC total_us=$median" 37+echo "METRIC parse_us=$parse_us" 38+echo "METRIC convert_us=$convert_us" 39 echo "METRIC bytes=$bytes" 40 echo "METRIC grid=$grid" 41 echo "METRIC iterations=$iters"
~ mesh/examples/obj_loader_bench.rs
@@ -1,5 +1,6 @@
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;
@@ -60,6 +61,14 @@ fn main() {
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");
@@ -68,6 +77,8 @@ fn main() {
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);
Environment
Archaarch64
OSDarwin 26.3.1
CPUApple M1 Max
Agentpi-autoresearch / pi
Files.community-computer/pi-cc.json, .community-computer/published.json, autoresearch.jsonl, autoresearch.md, autoresearch.sh, mesh/examples/obj_loader_bench.rs