Avoid over allocations & reallocations
As we know, vectors amortize the cost of adding new elements by reserving space for multiple elements when full. This is useful but may lead to allocating more memory than necessary.
relibc
generally avoids over allocations by reserving the exact amount of space when possible. I fixed a few areas that still over allocated or reallocated unnecessarily by leveraging iterators that are more likely to know sizes.
Here is a condensed, isolated code snippet of the changes made:
use std::{ffi::c_int, io::Write};
fn example_1() {
let s = "Redox";
let mut bad = s.as_bytes().to_vec();
// Allocates and doubles capacity to fit one byte
bad.push(b'\0');
assert_ne!(bad.len(), bad.capacity());
// Smartly allocates once
let good: Vec<_> = s.bytes().chain(Some(b'\0')).collect();
assert_eq!(good.len(), good.capacity());
}
fn example_2() {
let projects = ["Redox", "Orbital", "relibc"];
let mut v1 = Vec::default();
for project in projects {
println!(
"[before ({})] Capacity: {} Length: {}",
project,
v1.capacity(),
v1.len()
);
v1.extend(project.as_bytes());
v1.push(b'\0');
println!(
"[after ({})] Capacity: {} Length: {}",
project,
v1.capacity(),
v1.len()
);
}
// Avoids little allocations and over allocations
let v2: Vec<u8> = projects
.iter()
.flat_map(|project| project.as_bytes().iter().chain(b"\0"))
.copied()
.collect();
assert!(v1.into_iter().eq(v2.into_iter()));
}
fn example_3() {
let filedes: c_int = 111222;
// Reallocates at least once
let mut proc_path_old = b"/proc/self/fd/".to_vec();
write!(proc_path_old, "{}", filedes).unwrap();
proc_path_old.push(0);
// Simpler; doesn't realloc
let proc_path_exact = format!("/proc/self/fd/{}\0", filedes).into_bytes();
assert_eq!(proc_path_old.len(), proc_path_exact.len());
assert!(proc_path_old.into_iter().eq(proc_path_exact.into_iter()));
}
fn main() {
example_1();
example_2();
example_3();
}
Edited by Josh Megnauth