Memcpy generated for Vec::push(Default::default()) instead of initialization in-place
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
I was investigating a performance issue in code that initializes some larger structs from a binary protocol. The code tries hard to avoid unnecessary moves, for example by first creating default values and then initializing them, instead of returning structs by value. After a slight change to one of the structs there was a significant performance drop, which boiled down to an unnecessary call to memcpy.
Optimization of this memcpy seems to depend on the size of the struct and also on the ratio of zero-initialized fields to other fields.
In the following example, std_push_default initializes a struct on the stack and then calls memcpy to append it to the vector, while inlined_push_default initializes the struct in-place. The implementation of the latter should match the internals of Vec::push.
#[derive(Default)]
pub struct Foo {
a: [i64; 13],
b: [i64; 17],
c: Vec<i64>,
d: Vec<i64>,
}
#[no_mangle]
pub fn std_push_default(v: &mut Vec<Foo>) {
v.push(Default::default());
}
#[no_mangle]
pub fn inlined_push_default(v: &mut Vec<Foo>){
v.reserve(1);
unsafe {
v.as_mut_ptr().add(v.len()).write(Default::default());
v.set_len(v.len()+1);
}
}
#[no_mangle]
pub fn size_of_foo() -> usize {
std::mem::size_of::<Foo>()
}
Using rustc 1.78 in the compiler explorer. This might be a regression from 1.76, or the thresholds are slightly different.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Rust reproducer in the issue and compare generated code for rustc 1.76 and 1.78 in Compiler Explorer, varying the reported struct sizes and field ratios. Done means Vec::push(Default::default()) no longer emits the unnecessary memcpy and matches the in-place initialization behavior shown by the comparison function.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100