-
Notifications
You must be signed in to change notification settings - Fork 204
perf: fix O(n²) string allocations in fold operations #2257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Hi, @rnkrtt! Do you have a benchmark that shows any improvement with this change? |
|
@gabrielbosio hey! Consistent ~3.5x speedup across different data sizes. Benchmark code (click)use std::time::Instant;
use std::fmt::Write;
fn old_way(data: &[u8]) -> String {
data.iter()
.fold(String::new(), |acc, b| acc + &format!("{:02x}", b))
}
fn new_way(data: &[u8]) -> String {
data.iter().fold(
String::with_capacity(data.len() * 2),
|mut string, b| {
write!(&mut string, "{:02x}", b).unwrap();
string
},
)
}
fn bench(name: &str, size: usize, iterations: usize, f: fn(&[u8]) -> String) {
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
// Warmup
for _ in 0..5 {
let _ = f(&data);
}
// Actual measurement
let start = Instant::now();
for _ in 0..iterations {
let _ = f(&data);
}
let total_duration = start.elapsed();
let avg_duration = total_duration / iterations as u32;
println!("{} (size={}): {:?} avg over {} runs", name, size, avg_duration, iterations);
}
fn main() {
println!("Testing string fold performance...\n");
let configs = [
(100, 10000), // size, iterations
(1000, 1000),
(5000, 500),
(10000, 200),
];
for (size, iterations) in configs {
println!("--- Size: {} bytes ---", size);
bench("Old (fold + concat)", size, iterations, old_way);
bench("New (fold + write!) ", size, iterations, new_way);
// Calculate speedup
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
let start = Instant::now();
for _ in 0..iterations { let _ = old_way(&data); }
let old_time = start.elapsed();
let start = Instant::now();
for _ in 0..iterations { let _ = new_way(&data); }
let new_time = start.elapsed();
let speedup = old_time.as_secs_f64() / new_time.as_secs_f64();
println!("Speedup: {:.2}x faster\n", speedup);
}
}Run with: |
|
|
Benchmark Results for unmodified programs 🚀
|
|
Benchmarks of the repo don't show improvements so it would be great if you can add a benchmark to see how much this change improves execution time in context |
Description
Fixes O(n²) string allocations in fold operations
Found two places where we're doing string concatenation inside fold, which creates a new string allocation on every iteration. This gets really slow with large data.
Changes:
for hex serialization
reference lists
Both were doing the same anti-pattern just in different places