diff --git a/.jules/bolt.md b/.jules/bolt.md index 1d8c6be7..5034e016 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -17,3 +17,6 @@ ## 2025-02-12 - String allocation optimization **Learning:** In heavily used loops, especially for rendering or stringifying tasks, `push_str(&format!(...))` allocates temporary strings unnecessarily. Replacing it with `write!` directly onto the mutable `String` buffer (using `std::fmt::Write`) skips the intermediate allocations and improves performance significantly. **Action:** Use `std::fmt::Write` and the `write!` macro instead of `push_str(&format!(...))` when generating strings in loops to prevent temporary allocations and improve performance. +## 2024-08-01 - Bytecode Disassembler String Allocation Optimization +**Learning:** Formatting directly into a string buffer inside a tight loop with `write!(buffer, ...)` avoids unnecessary string heap allocations compared to `buffer.push_str(&format!(...))`. +**Action:** Always prefer formatting directly into the target String buffer when concatenating strings in loops in performance-sensitive paths like debuggers or disassemblers. diff --git a/compiler/bytecode/src/disassembler.rs b/compiler/bytecode/src/disassembler.rs index 2fa5a497..265b0ab6 100644 --- a/compiler/bytecode/src/disassembler.rs +++ b/compiler/bytecode/src/disassembler.rs @@ -28,45 +28,46 @@ impl BytecodeDisassembler { func.name, func.param_count, func.local_count, func.max_stack_size ); + // Bolt performance optimization: Avoid unnecessary string allocation for inst.op. + // `inst.op` implements `Debug`, so we can format it directly into the final string + // instead of pre-allocating intermediate Strings with `format!`. for (idx, inst) in func.chunk.instructions.iter().enumerate() { - let op_str = format!("{:?}", inst.op); - let mut line = format!("{:04} {:<15}", idx, op_str); + let _ = write!(output, "{:04} {:<15?}", idx, inst.op); for op in &inst.operands { match op { Operand::ConstantIndex(c_idx) => { if let Some(lit) = func.chunk.constants.get(*c_idx) { - let _ = write!(line, " #{:<3} ({:?})", c_idx, lit); + let _ = write!(output, " #{:<3} ({:?})", c_idx, lit); } else { - let _ = write!(line, " #{:<3} (invalid)", c_idx); + let _ = write!(output, " #{:<3} (invalid)", c_idx); } } Operand::LocalIndex(l_idx) => { if let Some(name) = func.debug_symbols.local_names.get(l_idx) { - let _ = write!(line, " local_{:<2} ({})", l_idx, name); + let _ = write!(output, " local_{:<2} ({})", l_idx, name); } else { - let _ = write!(line, " local_{}", l_idx); + let _ = write!(output, " local_{}", l_idx); } } Operand::JumpOffset(offset) => { let target = (idx as i32) + offset; - let _ = write!(line, " offset_{:<3} (target: {:04})", offset, target); + let _ = write!(output, " offset_{:<3} (target: {:04})", offset, target); } Operand::Count(n) => { - let _ = write!(line, " count_{}", n); + let _ = write!(output, " count_{}", n); } Operand::GlobalIndex(g_idx) => { - let _ = write!(line, " global_{}", g_idx); + let _ = write!(output, " global_{}", g_idx); } Operand::Register(r_idx) => { - let _ = write!(line, " reg_{}", r_idx); + let _ = write!(output, " reg_{}", r_idx); } Operand::FunctionIndex(f_idx) => { - let _ = write!(line, " func_{}", f_idx); + let _ = write!(output, " func_{}", f_idx); } } } - output.push_str(&line); output.push('\n'); } output