Question about memory consumption of a reference to a constant

The question is how much memory consumption will SMALL_SLICE cause in the following code? Will the compiler optimize out the unread part?

const ARRAY_100KB: [u8; 102400] = [0; 102400];
static SMALL_SLICE: &[u8] = ARRAY_100KB.split_at(5).0;

Google Gemini said 21 bytes in total, which combined by 16 byte(fat pointer) + 5 byte(underlying data), but I'm not sure.

BTW, is that a better way to get a range of elements of a slice in constant environment?

Why ask an LLM about the behavior of a compiler if you could just run the compiler?

https://rust.godbolt.org/z/ss1dfoz78


The answer seems to be that it generally does not get rid of the unused parts of the 102400 bytes long array, but instead keeps the whole thing, unless it can optimize out the entire thing.

1 Like

Here is a way to keep only the wanted data explicitly, by copying it into a new array:

const ARRAY_100KB: [u8; 102400] = [0; 102400];
const SMALL_SLICE: &[u8] = ARRAY_100KB.split_at(5).0;
static REAL_SMALL_SLICE: &[u8] = &{
    *SMALL_SLICE.split_first_chunk::<{SMALL_SLICE.len()}>().unwrap().0
};
2 Likes

Thanks. Because I don't know how to use that tool :smiling_face_with_tear:.

Here’s another example showing the subtle ways in which optimizations can already be inhibited:

https://rust.godbolt.org/z/s1fMnvoda

const LONG_ARRAY: [u8; 102400] = [0; 102400];
static SMALL_SLICE: &[u8] = LONG_ARRAY.split_at(5).0;

// using this doesn’t create the whole data: .zero   102400
#[cfg(version1)]
#[inline(never)]
fn print_slice(x: &[u8]) {
    for i in x {
        println!("{}", {*i});
    }
}

// using this creates the whole data: .zero   102400
#[cfg(version2)]
#[inline(never)]
fn print_slice(x: &[u8]) {
    for i in x {
        println!("{}", *i);
    }
}

#[unsafe(no_mangle)]
pub fn do_printing() {
    print_slice(SMALL_SLICE);
}

basically anything that can reveil the memory address of (an instantiation of) the original [0; 102400] constant value (static-promoted as part of the SMALL_SLICE initialized) to “arbitrary/unknown code”, the optimization goes away.

In the code example above, something like

for i in x {
    println!("{}", *i);
}

makes the println macro produce &*i pointing to the u8 still in its original place as part of the long array - and then pass it to the dyn Trait-based formatting infrastructure, so the optimized doesn’t know whether the address is observed, and conservatively prevents optimizing it out.

Adding { } would force a move, and with the u8 in a new location, all accesses to the original [0; 102400] are visible to the LLVM optimizer.

3 Likes

No problem! Just ask Gemini to help you with using godbolt.org :grinning_cat_with_smiling_eyes: - and perhaps even with interpreting the results.

Thanks! That's a good workaround.