rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.24k stars 12.71k forks source link

Vec-growing logic emitted for Vecs which never grow #118563

Open LingMan opened 11 months ago

LingMan commented 11 months ago

Consider these two examples:

pub fn foo(sizes: &[u64]) -> u64 {
    let mut top = Vec::with_capacity(1);
    for &size in sizes {
        /*if top.capacity() <= top.len() {
            unsafe { std::hint::unreachable_unchecked() }
        }*/
        top.push(size);
        top.pop();
    }
    top.iter().sum()
}
pub fn bar(sizes: &[u64]) -> u64 {
    const TOP_ELEMENTS: usize = 3;
    let mut top = Vec::with_capacity(TOP_ELEMENTS + 1);
    for &size in sizes {
        /*if top.capacity() <= top.len() {
            unsafe { std::hint::unreachable_unchecked() }
        }*/
        let i = 0; //top.partition_point(|&cur| cur > size);
        top.insert(i, size);
        top.truncate(TOP_ELEMENTS);
    }
    top.iter().sum()
}

Godbolt: https://rust.godbolt.org/z/PhrMr7dYr

Neither are ever going to grow their Vec from the initial size. Yet, RawVec::reserve_for_push and friends are emitted when compiling. Uncommenting the unreachable_unchecked hints allows the optimizer to clean everything up. Might be asking a lot of the compiler but it would be great if it could figure that out by itself.

Tested with Rust 1.74 and with 1.76.0-nightly (87e1447aa 2023-11-30).

the8472 commented 11 months ago

On nightly you can use push_within_capacity to avoid the resizing machinery.

LingMan commented 11 months ago

Not really, I need the insert semantic. The version with push is just an example I came up with while poking at it. And I'm on stable.

Will probably use the arrayvec crate for now.

jhorstmann commented 11 months ago

Looks similar to #114334, but that had pop followed by push.

105156 and #82801 are also related to better capacity tracking.