rust-lang / rust-lang/rust

Memcpy generated for Vec::push(Default::default()) instead of initialization in-place

Open
#125,632 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-codegen C-optimization T-compiler
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.