Skip to content

Commit 7266a81

Browse files
authored
Merge pull request #157 from Tcode-Motion/jules/bolt-optimize-formatter-allocation-6914985605802378107
⚡ Bolt: [performance improvement] optimize string allocations in formatter
2 parents 2b49028 + 70c6c91 commit 7266a81

2 files changed

Lines changed: 89 additions & 65 deletions

File tree

‎.jules/bolt.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,3 @@
2020
## 2024-08-01 - Bytecode Disassembler String Allocation Optimization
2121
**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!(...))`.
2222
**Action:** Always prefer formatting directly into the target String buffer when concatenating strings in loops in performance-sensitive paths like debuggers or disassemblers.
23-

‎tools/formatter/src/lib.rs‎

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fmt::Write;
12
use techscript_ast::{DSLBlock, DSLChild, Program, Statement};
23

34
pub trait Formatter {
@@ -20,136 +21,160 @@ impl DocumentFormatter {
2021
self.format(&program)
2122
}
2223

23-
fn format_indent(&self, indent: usize) -> String {
24-
" ".repeat(indent * self.indent_size)
24+
fn format_indent(&self, indent: usize, buf: &mut String) {
25+
for _ in 0..(indent * self.indent_size) {
26+
buf.push(' ');
27+
}
2528
}
2629

27-
fn format_dsl_block(&self, block: &DSLBlock, indent: usize) -> String {
28-
let mut output = String::new();
29-
let base = self.format_indent(indent);
30-
31-
output.push_str(&base);
32-
output.push_str(&block.kind);
30+
fn format_dsl_block(&self, block: &DSLBlock, indent: usize, buf: &mut String) {
31+
self.format_indent(indent, buf);
32+
buf.push_str(&block.kind);
3333
for arg in &block.args {
34-
output.push(' ');
35-
output.push_str(&self.format_expr(arg));
34+
buf.push(' ');
35+
self.format_expr(arg, buf);
3636
}
37-
output.push('\n');
37+
buf.push('\n');
3838

3939
for prop in &block.properties {
40-
output.push_str(&self.format_indent(indent + 1));
41-
output.push_str(&prop.name);
40+
self.format_indent(indent + 1, buf);
41+
buf.push_str(&prop.name);
4242
if let Some(ref val) = prop.value {
43-
output.push(' ');
44-
output.push_str(&self.format_expr(val));
43+
buf.push(' ');
44+
self.format_expr(val, buf);
4545
}
46-
output.push('\n');
46+
buf.push('\n');
4747
}
4848

4949
for child in &block.children {
5050
match child {
5151
DSLChild::Block(sub_block) => {
52-
output.push_str(&self.format_dsl_block(sub_block, indent + 1));
52+
self.format_dsl_block(sub_block, indent + 1, buf);
5353
}
5454
DSLChild::Code(code_block) => {
55-
output.push_str(&self.format_indent(indent + 1));
56-
output.push_str("code\n");
55+
self.format_indent(indent + 1, buf);
56+
buf.push_str("code\n");
5757
for stmt in &code_block.statements {
58-
output.push_str(&self.format_stmt(stmt, indent + 2));
58+
self.format_stmt(stmt, indent + 2, buf);
5959
}
6060
}
6161
DSLChild::Property(prop) => {
62-
output.push_str(&self.format_indent(indent + 1));
63-
output.push_str(&prop.name);
62+
self.format_indent(indent + 1, buf);
63+
buf.push_str(&prop.name);
6464
if let Some(ref val) = prop.value {
65-
output.push(' ');
66-
output.push_str(&self.format_expr(val));
65+
buf.push(' ');
66+
self.format_expr(val, buf);
6767
}
68-
output.push('\n');
68+
buf.push('\n');
6969
}
7070
}
7171
}
7272

73-
output.push_str(&base);
74-
output.push_str("end\n");
75-
output
73+
self.format_indent(indent, buf);
74+
buf.push_str("end\n");
7675
}
7776

78-
fn format_expr(&self, expr: &techscript_ast::Expression) -> String {
77+
fn format_expr(&self, expr: &techscript_ast::Expression, buf: &mut String) {
7978
match expr {
80-
techscript_ast::Expression::Literal(lit) => self.format_lit(&lit.value),
81-
techscript_ast::Expression::Identifier(ident) => ident.name.clone(),
79+
techscript_ast::Expression::Literal(lit) => self.format_lit(&lit.value, buf),
80+
techscript_ast::Expression::Identifier(ident) => buf.push_str(&ident.name),
8281
techscript_ast::Expression::FString(fs) => {
83-
let mut s = "f\"".to_string();
82+
buf.push_str("f\"");
8483
for part in &fs.parts {
8584
match part {
86-
techscript_ast::FStringPart::Literal(l) => s.push_str(l),
87-
techscript_ast::FStringPart::Expr(_) => s.push_str("{}"),
85+
techscript_ast::FStringPart::Literal(l) => buf.push_str(l),
86+
techscript_ast::FStringPart::Expr(_) => buf.push_str("{}"),
8887
}
8988
}
90-
s.push('"');
91-
s
89+
buf.push('"');
90+
}
91+
_ => {
92+
let _ = write!(buf, "{:?}", expr);
9293
}
93-
_ => format!("{:?}", expr),
9494
}
9595
}
9696

97-
fn format_lit(&self, lit: &techscript_ast::LiteralVal) -> String {
97+
fn format_lit(&self, lit: &techscript_ast::LiteralVal, buf: &mut String) {
9898
match lit {
99-
techscript_ast::LiteralVal::Str(s) => format!("\"{}\"", s),
100-
techscript_ast::LiteralVal::Int(i) => i.to_string(),
101-
techscript_ast::LiteralVal::Float(f) => f.to_string(),
102-
techscript_ast::LiteralVal::Bool(b) => b.to_string(),
103-
techscript_ast::LiteralVal::None => "none".to_string(),
99+
techscript_ast::LiteralVal::Str(s) => {
100+
buf.push('"');
101+
buf.push_str(s);
102+
buf.push('"');
103+
}
104+
techscript_ast::LiteralVal::Int(i) => {
105+
let _ = write!(buf, "{}", i);
106+
}
107+
techscript_ast::LiteralVal::Float(f) => {
108+
let _ = write!(buf, "{}", f);
109+
}
110+
techscript_ast::LiteralVal::Bool(b) => {
111+
if *b {
112+
buf.push_str("true");
113+
} else {
114+
buf.push_str("false");
115+
}
116+
}
117+
techscript_ast::LiteralVal::None => buf.push_str("none"),
104118
}
105119
}
106120

107-
fn format_stmt(&self, stmt: &Statement, indent: usize) -> String {
108-
let base = self.format_indent(indent);
121+
fn format_stmt(&self, stmt: &Statement, indent: usize, buf: &mut String) {
109122
match stmt {
110-
Statement::DSL(block) => self.format_dsl_block(block, indent),
123+
Statement::DSL(block) => self.format_dsl_block(block, indent, buf),
111124
Statement::VarDecl(decl) => {
125+
self.format_indent(indent, buf);
126+
buf.push_str("make ");
112127
let name = match &decl.pattern {
113128
techscript_ast::Pattern::Single(ident) => ident.name.clone(),
114129
_ => "<pat>".to_string(),
115130
};
116-
format!(
117-
"{}make {} = {}\n",
118-
base,
119-
name,
120-
self.format_expr(&decl.initializer)
121-
)
131+
buf.push_str(&name);
132+
buf.push_str(" = ");
133+
self.format_expr(&decl.initializer, buf);
134+
buf.push('\n');
122135
}
123136
Statement::ConstDecl(decl) => {
137+
self.format_indent(indent, buf);
138+
buf.push_str("const ");
124139
let name = match &decl.pattern {
125140
techscript_ast::Pattern::Single(ident) => ident.name.clone(),
126141
_ => "<pat>".to_string(),
127142
};
128-
format!(
129-
"{}const {} = {}\n",
130-
base,
131-
name,
132-
self.format_expr(&decl.initializer)
133-
)
143+
buf.push_str(&name);
144+
buf.push_str(" = ");
145+
self.format_expr(&decl.initializer, buf);
146+
buf.push('\n');
147+
}
148+
Statement::Say(s) => {
149+
self.format_indent(indent, buf);
150+
buf.push_str("say ");
151+
self.format_expr(&s.value, buf);
152+
buf.push('\n');
134153
}
135-
Statement::Say(s) => format!("{}say {}\n", base, self.format_expr(&s.value)),
136154
Statement::Return(ret) => {
155+
self.format_indent(indent, buf);
137156
if let Some(ref val) = ret.value {
138-
format!("{}return {}\n", base, self.format_expr(val))
157+
buf.push_str("return ");
158+
self.format_expr(val, buf);
159+
buf.push('\n');
139160
} else {
140-
format!("{}return\n", base)
161+
buf.push_str("return\n");
141162
}
142163
}
143-
_ => format!("{}<stmt>\n", base),
164+
_ => {
165+
self.format_indent(indent, buf);
166+
buf.push_str("<stmt>\n");
167+
}
144168
}
145169
}
146170
}
147171

148172
impl Formatter for DocumentFormatter {
149173
fn format(&self, program: &Program) -> String {
150-
let mut output = String::new();
174+
// Estimate 64 bytes per statement as a reasonable baseline capacity
175+
let mut output = String::with_capacity(program.statements.len() * 64);
151176
for stmt in &program.statements {
152-
output.push_str(&self.format_stmt(stmt, 0));
177+
self.format_stmt(stmt, 0, &mut output);
153178
}
154179
output
155180
}

0 commit comments

Comments
 (0)