Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
25 changes: 13 additions & 12 deletions compiler/bytecode/src/disassembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading