-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Cut heap allocations on the hot path of text rendering (labels, headings, buttons, hyperlinks) and template interpolations. Adopt a borrow-first model, reusable buffers, and small containers to minimize churn — especially important for WebAssembly and low-power devices.
- Fewer allocations → lower GC/allocator pressure, better frame times and battery life.
- Text + interpolation is a dominant path in typical EFx UIs.
- A leaner path benefits both native and
wasm32-unknown-unknownbuilds.
Scope
- Text nodes:
<Label>,<Heading>,<Button>,<Hyperlink>, plus any text-emitting tag. - Interpolations: sequences like
"Hello {name}, {count} unread". - Attribute normalization: avoid transient
Stringduring parse/map where possible.
Non-goals (here):
- Changing public semantics or tag behavior.
- Introducing heavy dependencies by default (feature-gate if needed).
- Visual/behavioral changes.
Proposed approach
- Borrow-first data model (
&str/Cow<'a, str>)
Plumb lifetimes so that text/attribute values stay borrowed whenever possible and only allocate when required (e.g., computed strings, concatenations).
// Example: text segment representation
enum TextSeg<'a> {
Lit(&'a str), // borrowed literal slice
Expr(Cow<'a, str>), // computed, but can be borrowed if source is &str
}- Reusable render buffers (per-frame)
Introduce a lightweightRenderBufinside render context (TLS or passed mutably) with pre-grownStringbuffers reused across nodes:
pub struct RenderBuf {
pub text: String, // reused for formatting text nodes
pub scratch: String, // misc scratch for adapters
}
impl RenderBuf {
#[inline] pub fn clear_preserve(&mut self) {
self.text.clear(); self.scratch.clear();
}
}- Replace
format!(...)in hot paths withwrite!(&mut buf.text, ...)to reuse capacity.
- Streaming interpolation
Compile text nodes to a small iterator over segments; at render time, stitch intoRenderBuf::textviawrite!:
buf.text.clear();
for seg in segs {
match seg {
TextSeg::Lit(s) => buf.text.push_str(s),
TextSeg::Expr(s) => buf.text.push_str(&s),
}
}
ui.label(&buf.text); // pass &str, no extra allocation- Prefer passing
&strdirectly toeguiAPIs where accepted (Into<WidgetText>from&stravoids another copy).
-
Small containers for small data
UseSmallVec<[TextSeg; 4]>(or equivalent) for typical short strings to avoid heap for ≤4 segments.
Reserve exactly when size is known:vec.reserve_exact(n)to prevent over-allocation. -
Avoid transient clones
Systematically replace.to_string()/.to_owned()/String::fromin hot paths with borrowed alternatives orCow<'_, str>; only allocate on boundary APIs that require owning strings. -
Attribute adapters: zero-copy fast path
- Parse numeric/enum attributes directly from
&strviaFromStrwithout intermediateString. - Map known keywords through
matchon byte slices; avoid case conversions unless necessary (or precompute case-folded keys at parse time).
-
Inlining & micro-tweaks
Annotate tiny helpers with#[inline]; avoid iterator materialization whereforloops generate less overhead; remove temporaryVecwhere a direct walk suffices. -
Benchmark & allocation counting
-
Add
criterionbenches for:label_literal(short + long).label_interpol_1/3/5(number of{}segments).button_literal,heading_h1.- A small template scene (Column+Row) to approximate real usage.
-
Optional
bench-allocfeature: swap global allocator or use a counting wrapper to trackallocs/opin benches (build-only, not shipped by default).
Tasks
- Introduce
RenderBuf(reusedStrings) and thread it through render context. - Switch text interpolation path to streaming write (
write!) intoRenderBuf. - Convert text/attribute representations to borrow-first (
&str/Cow<'_, str>). - Replace hot-path
format!/.to_string()with borrowed or buffered alternatives. - Adopt
SmallVec(or equivalent) for text segment storage; reserve capacities where known. - Optimize attribute adapters: direct parse from
&str, keywordmatchwithout extra copies. - Add
criterionbenches + (optional) allocation counter underbench-alloc. - Document the approach in a brief DEV note: “Why buffers & borrow-first”.
- Ensure examples build on
wasm32-unknown-unknown(no runtime launch).