Experiment: fa476da
rad experiment show fa476da3d9847d03fac4e5d2f298f012e668645e
by lftherioslftherios@did:key:z6MkupQHdQky5atyAu9zF8dUu1EavUjckDgCdZLH8y2W8teE · Apr 21 14:35 2026
Inline Vertex field processing in the named and unnamed loops instead of routing each field through a generic helper.
Measurements
MetricBaselineThe benchmark measurement of the unmodified codeCandidateThe code with the proposed optimization appliedCandidate/Parent ΔThe performance change from baseline to candidate
total_µs (µs) primary
Optimize the `luminance-derive` procedural macro implementation for lower code generation time on a synthetic workload that repeatedly parses and expands large `Semantics`, `Vertex`, and `UniformInterface` derive inputs.
541087.000 µs (n=1)532880.000 µs (n=1)-1.51%
semantics_µs secondary214890.000 (n=1)215102.000 (n=1)+0.09%
uniform_µs secondary248647.000 (n=1)249797.000 (n=1)+0.46%
vertex_µs secondary77550.000 (n=1)67981.000 (n=1)-12.33% REGRESSED
Annotations
hypothesisVertex still paid per-field helper and generic-option plumbing overhead during derive expansion, so folding the work directly into the field loops should reduce the hot per-field path.
idea_backlog_prunedRemoved the Vertex-fusion idea because direct in-loop field handling captured that path's main remaining win.
observed_effectStrong vertex-specific win with total benchmark improvement; semantics and uniform remained near the previous best, confirming the gain came from Vertex codegen.
Base CommitThe starting commit before the optimization was applied428547431bf34ee6b8053c205c704dad8929ed4a
Candidate CommitThe code with the proposed optimization appliedb25631ee32074173a3ae4572288b44331f6b5da1
Bench scriptba27946dedc9
bench_cmdbash autoresearch.sh
Schemav5
Diff
~ .community-computer/published.json
@@ -24,6 +24,11 @@
24 "base": "bdce49a66c74c9a8f993578e31a7c9aa0fac52bb", 25 "head": "5637ecc41deeda7a6c37a28a721fcb49a6b11010", 26 "cob": "3c20aff5d9ebe67bb3376c8fb0b433b281436f07" 27+ }, 28+ { 29+ "base": "5637ecc41deeda7a6c37a28a721fcb49a6b11010", 30+ "head": "428547431bf34ee6b8053c205c704dad8929ed4a", 31+ "cob": "b14aa7bd85cd31bc51fd9650dbf10315e58ba03d" 32 } 33 ] 34 }
~ autoresearch.ideas.md
@@ -1,2 +1,2 @@
1 - Try a shared fast attribute decoder in `attrib.rs` for `sem`, `vertex`, and `uniform` that returns parsed small structs while preserving current error messages; this may reduce both duplicated logic and any remaining parsing overhead. 2-- Investigate whether `Vertex` can mirror the successful `Semantics` fusion approach by collecting field metadata into simple arrays/records first and expanding from those directly instead of accumulating per-field token fragments. 2+- Investigate whether `UniformInterface` can avoid per-field `TokenStream` statement accumulation, perhaps by collecting simpler field records first and emitting declarations from those directly while preserving declaration order and behavior.
~ autoresearch.jsonl
@@ -5,3 +5,4 @@
5 {"run":4,"commit":"bdce49a","metric":577503,"metrics":{"semantics_µs":241770,"vertex_µs":76188,"uniform_µs":259545},"status":"discard","description":"Preallocate token vector capacities from known field and variant counts to reduce small Vec growth overhead.","timestamp":1776781735933,"segment":0,"confidence":34.74205095809644,"iterationTokens":6468,"asi":{"hypothesis":"Some remaining overhead might come from repeated Vec growth while collecting quote fragments, so reserving exact capacities could yield a cheap incremental win.","rollback_reason":"The capacity reservations were within benchmark noise and slightly worse than the current best run, so they are not worth keeping.","next_action_hint":"Focus on reducing token construction/allocation work or simplifying generated quote structure rather than container bookkeeping."}} 6 {"run":5,"commit":"bdce49a","metric":580006,"metrics":{"semantics_µs":243093,"vertex_µs":78819,"uniform_µs":258094},"status":"discard","description":"Inline Vertex field normalized parsing to avoid the generic attribute helper on the hot per-field path.","timestamp":1776781806221,"segment":0,"confidence":47.54755043227666,"iterationTokens":6596,"asi":{"hypothesis":"Even one generic helper call per vertex field might add measurable overhead, so specializing normalized parsing could further reduce vertex_µs.","rollback_reason":"Specializing the per-field parser did not beat the current best and slightly regressed the benchmark overall.","next_action_hint":"Investigate token generation/allocation costs next; attribute parsing likely delivered most of the easy win already.","ideas_backlog_updated":true}} 7 {"run":6,"commit":"5637ecc","metric":569920,"metrics":{"semantics_µs":241609,"vertex_µs":78154,"uniform_µs":250157},"status":"keep","description":"Reduce intermediate quote fragment churn by emitting Semantics, Vertex, and UniformInterface tokens more directly.","timestamp":1776781959754,"segment":0,"confidence":31.562080841036796,"iterationTokens":15670,"asi":{"hypothesis":"After the parsing wins, remaining overhead likely comes from constructing and nesting many temporary quote fragments, so flattening token assembly should cut allocation/churn in code generation.","observed_effect":"Best result so far; the biggest gain showed up in UniformInterface token generation while Semantics stayed roughly flat and Vertex regressed slightly but was outweighed by the uniform win.","idea_backlog_pruned":"Replaced the broad direct-token-assembly backlog item with narrower follow-ups for shared attribute decoding and Semantics-specific fusion."}} 8+{"run":7,"commit":"4285474","metric":541087,"metrics":{"semantics_µs":214890,"vertex_µs":77550,"uniform_µs":248647},"status":"keep","description":"Fuse Semantics code generation around parsed variant data arrays instead of building multiple per-variant TokenStream vectors.","timestamp":1776782041293,"segment":0,"confidence":38.45288652952887,"iterationTokens":7792,"asi":{"hypothesis":"Semantics still spent time creating several parallel TokenStream vectors; expanding directly from stored parsed data should reduce quote/allocation overhead in that hot path.","observed_effect":"Large semantics-specific win with a meaningful follow-on total improvement; uniform also improved slightly while vertex stayed near prior levels.","idea_backlog_pruned":"Removed the Semantics-specific fusion backlog item because it is now implemented; next promising path is analogous Vertex fusion or shared attribute decoding."}}
~ autoresearch.md
@@ -35,3 +35,4 @@ Optimize the `luminance-derive` procedural macro implementation for lower code g
35 - Applied the same single-pass parsing idea to `Vertex` type-level attributes (`sem` and `instanced`). This produced a modest extra win, mainly by trimming `vertex_µs` and a bit of total overhead. 36 - Reduced intermediate `quote!` fragment churn by emitting token fragments more directly in `Semantics`, `UniformInterface`, and `Vertex` code assembly. This produced the best total result so far, with most of the gain coming from a notably faster `uniform_µs` path. 37 - Fused `Semantics` token generation further by collecting parsed variant data once and expanding directly from those arrays instead of first building multiple per-variant `TokenStream` vectors. This produced a large additional `semantics_µs` win and the strongest total result so far. 38+- Inlined `Vertex` field processing into the named/unnamed loops instead of routing every field through a generic helper. This cut `vertex_µs` sharply and produced another best total result.
~ luminance-derive/src/vertex.rs
@@ -128,18 +128,39 @@ where A: Iterator<Item = &'a Attribute> {
128 let mut indexed_vertex_attrib_descs = Vec::new(); 129 let mut fields_types = Vec::new(); 130 131- for (i, field) in unnamed_fields.unnamed.into_iter().enumerate() { 132- let field_ident = format_ident!("field_{}", i); 133- 134- process_field( 135- &field, 136- field_ident, 137- &sem_type, 138- &instancing, 139- &mut indexed_vertex_attrib_descs, 140- &mut fields_types, 141- None 142- )?; 131+ for field in unnamed_fields.unnamed.into_iter() { 132+ let normalized = get_field_attr_once( 133+ &ident, 134+ &field.attrs, 135+ "vertex", 136+ "normalized", 137+ KNOWN_SUBKEYS 138+ ).map(|b: LitBool| b.value) 139+ .or_else(|e| match e { 140+ AttrError::CannotFindAttribute(..) => Ok(false), 141+ _ => Err(e) 142+ }) 143+ .map_err(StructImplError::FieldError)?; 144+ 145+ let field_ty = field.ty; 146+ indexed_vertex_attrib_descs.push(if normalized { 147+ quote! { 148+ luminance::vertex::VertexBufferDesc::new::<#sem_type>( 149+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS, 150+ #instancing, 151+ (<#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC).normalize(), 152+ ) 153+ } 154+ } else { 155+ quote! { 156+ luminance::vertex::VertexBufferDesc::new::<#sem_type>( 157+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS, 158+ #instancing, 159+ <#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC, 160+ ) 161+ } 162+ }); 163+ fields_types.push(field_ty); 164 } 165 166 let output = process_struct(ident, indexed_vertex_attrib_descs, Vec::new(), fields_types);
@@ -152,17 +173,40 @@ where A: Iterator<Item = &'a Attribute> {
173 let mut fields_names = Vec::new(); 174 175 for field in named_fields.named { 155- let field_ident = field.ident.clone().unwrap(); 156- 157- process_field( 158- &field, 159- field_ident, 160- &sem_type, 161- &instancing, 162- &mut indexed_vertex_attrib_descs, 163- &mut fields_types, 164- &mut fields_names 165- )?; 176+ let normalized = get_field_attr_once( 177+ &ident, 178+ &field.attrs, 179+ "vertex", 180+ "normalized", 181+ KNOWN_SUBKEYS 182+ ).map(|b: LitBool| b.value) 183+ .or_else(|e| match e { 184+ AttrError::CannotFindAttribute(..) => Ok(false), 185+ _ => Err(e) 186+ }) 187+ .map_err(StructImplError::FieldError)?; 188+ 189+ let field_ident = field.ident.unwrap(); 190+ let field_ty = field.ty; 191+ indexed_vertex_attrib_descs.push(if normalized { 192+ quote! { 193+ luminance::vertex::VertexBufferDesc::new::<#sem_type>( 194+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS, 195+ #instancing, 196+ (<#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC).normalize(), 197+ ) 198+ } 199+ } else { 200+ quote! { 201+ luminance::vertex::VertexBufferDesc::new::<#sem_type>( 202+ <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS, 203+ #instancing, 204+ <#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC, 205+ ) 206+ } 207+ }); 208+ fields_types.push(field_ty); 209+ fields_names.push(field_ident); 210 } 211 212 let output = process_struct(ident, indexed_vertex_attrib_descs, fields_names, fields_types);
@@ -173,55 +217,6 @@ where A: Iterator<Item = &'a Attribute> {
217 } 218 } 219 176-fn process_field<'a, FN>( 177- field: &Field, 178- ident: Ident, 179- sem_type: &Type, 180- instancing: &proc_macro2::TokenStream, 181- indexed_vertex_attrib_descs: &mut Vec<proc_macro2::TokenStream>, 182- fields_types: &mut Vec<Type>, 183- fields_names: FN, 184-) -> Result<(), StructImplError> 185-where FN: Into<Option<&'a mut Vec<Ident>>> { 186- // search for the normalized argument; if not there, we don’t normalize anything 187- let normalized = get_field_attr_once( 188- &ident, 189- &field.attrs, 190- "vertex", 191- "normalized", 192- KNOWN_SUBKEYS 193- ).map(|b: LitBool| b.value) 194- .or_else(|e| match e { 195- AttrError::CannotFindAttribute(..) => Ok(false), 196- _ => Err(e) 197- }) 198- .map_err(StructImplError::FieldError)?; 199- 200- let field_ty = &field.ty; 201- let vertex_attrib_desc = if normalized { 202- quote!{ (<#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC).normalize() } 203- } else { 204- quote!{ <#field_ty as luminance::vertex::VertexAttrib>::VERTEX_ATTRIB_DESC } 205- }; 206- 207- let indexed_vertex_attrib_desc_q = quote!{ 208- luminance::vertex::VertexBufferDesc::new::<#sem_type>( 209- <#field_ty as luminance::vertex::HasSemantics>::SEMANTICS, 210- #instancing, 211- #vertex_attrib_desc, 212- ) 213- }; 214- 215- indexed_vertex_attrib_descs.push(indexed_vertex_attrib_desc_q); 216- fields_types.push(field_ty.clone()); 217- 218- if let Some(fields_names) = fields_names.into() { 219- fields_names.push(ident); 220- } 221- 222- Ok(()) 223-} 224- 220 /// Process the output struct. 221 /// 222 /// If fields_names is empty, it is assumed to be a struct-tuple.
Environment
Archaarch64
OSDarwin 26.3.1
CPUApple M1 Max
Agentpi-autoresearch / pi
Files.community-computer/published.json, autoresearch.ideas.md, autoresearch.jsonl, autoresearch.md, luminance-derive/src/vertex.rs