Skip to content

Commit 5a8633e

Browse files
⚡ Bolt: Optimize BytecodeDisassembler string allocation
Replaced intermediate `format!` allocations with direct `write!` invocations inside the `BytecodeDisassembler`'s formatting loop, reducing unnecessary heap allocations. Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com>
1 parent 95d2768 commit 5a8633e

2 files changed

Lines changed: 16 additions & 12 deletions

File tree

‎.jules/bolt.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@
1717
## 2025-02-12 - String allocation optimization
1818
**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.
1919
**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.
20+
## 2024-08-01 - Bytecode Disassembler String Allocation Optimization
21+
**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!(...))`.
22+
**Action:** Always prefer formatting directly into the target String buffer when concatenating strings in loops in performance-sensitive paths like debuggers or disassemblers.

‎compiler/bytecode/src/disassembler.rs‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,45 +28,46 @@ impl BytecodeDisassembler {
2828
func.name, func.param_count, func.local_count, func.max_stack_size
2929
);
3030

31+
// Bolt performance optimization: Avoid unnecessary string allocation for inst.op.
32+
// `inst.op` implements `Debug`, so we can format it directly into the final string
33+
// instead of pre-allocating intermediate Strings with `format!`.
3134
for (idx, inst) in func.chunk.instructions.iter().enumerate() {
32-
let op_str = format!("{:?}", inst.op);
33-
let mut line = format!("{:04} {:<15}", idx, op_str);
35+
let _ = write!(output, "{:04} {:<15?}", idx, inst.op);
3436

3537
for op in &inst.operands {
3638
match op {
3739
Operand::ConstantIndex(c_idx) => {
3840
if let Some(lit) = func.chunk.constants.get(*c_idx) {
39-
let _ = write!(line, " #{:<3} ({:?})", c_idx, lit);
41+
let _ = write!(output, " #{:<3} ({:?})", c_idx, lit);
4042
} else {
41-
let _ = write!(line, " #{:<3} (invalid)", c_idx);
43+
let _ = write!(output, " #{:<3} (invalid)", c_idx);
4244
}
4345
}
4446
Operand::LocalIndex(l_idx) => {
4547
if let Some(name) = func.debug_symbols.local_names.get(l_idx) {
46-
let _ = write!(line, " local_{:<2} ({})", l_idx, name);
48+
let _ = write!(output, " local_{:<2} ({})", l_idx, name);
4749
} else {
48-
let _ = write!(line, " local_{}", l_idx);
50+
let _ = write!(output, " local_{}", l_idx);
4951
}
5052
}
5153
Operand::JumpOffset(offset) => {
5254
let target = (idx as i32) + offset;
53-
let _ = write!(line, " offset_{:<3} (target: {:04})", offset, target);
55+
let _ = write!(output, " offset_{:<3} (target: {:04})", offset, target);
5456
}
5557
Operand::Count(n) => {
56-
let _ = write!(line, " count_{}", n);
58+
let _ = write!(output, " count_{}", n);
5759
}
5860
Operand::GlobalIndex(g_idx) => {
59-
let _ = write!(line, " global_{}", g_idx);
61+
let _ = write!(output, " global_{}", g_idx);
6062
}
6163
Operand::Register(r_idx) => {
62-
let _ = write!(line, " reg_{}", r_idx);
64+
let _ = write!(output, " reg_{}", r_idx);
6365
}
6466
Operand::FunctionIndex(f_idx) => {
65-
let _ = write!(line, " func_{}", f_idx);
67+
let _ = write!(output, " func_{}", f_idx);
6668
}
6769
}
6870
}
69-
output.push_str(&line);
7071
output.push('\n');
7172
}
7273
output

0 commit comments

Comments
 (0)