Git Product home page Git Product logo

Comments (7)

Jerrody avatar Jerrody commented on June 20, 2024 1

The problem with your desired optimization, @Jerrody, is ultimately because of this program:

// pointer-escape-kinda.rs
use std::{error, ptr};
#[derive(Debug)]
struct MoveArray<T, const N: usize>([T; N]);

#[inline(never)]
pub fn main() -> Result<(), Box<dyn error::Error>> {
    let place_0 = MoveArray::<i32, 3>([1, 2, 3]);
    let ptr = ptr::addr_of!(place_0) as *mut i32;
    let place_1 = place_0;
    unsafe { ptr.write(5) };
    println!("{:?}", unsafe { (ptr as *mut [i32; 3]).read() });
    println!("{:?}", &place_1);
    Ok(())
}

Note that I do something that one might normally consider "impossible": mutate an immutable location. Indeed, if you run it via Tools -> Miri, Miri will complain about it being UB, currently. This being true makes some other semantics harder to implement, so whether this should be permitted is considered an open question in Rust's operational semantics:

It's not implausible, though, due to the fact you derive the pointer from &Vec<i32>, which has less questions than the ptr::addr_of! I just used.

Thanks for the explanation. Sorry that I'm dumb in understanding these things, thanks for patient.

from rust.

workingjubilee avatar workingjubilee commented on June 20, 2024

Behaviour is the same in debug and release.

Code

fn main() {
    let vec1 = vec![1, 2, 3];
    println!("{:p}", &vec1);
    let vec2 = vec1;
    println!("{:p}", &vec2);
}

Output

0x7ffe2bd06e90 0x7ffe2bd06e70

This is just waste of CPU time on performing this machinery, where basically changes only identifier.

Do you have a version of this that does not require printing the pointer values?

Consider this Godbolt: https://rust.godbolt.org/z/79vGhfcMv

I refer to your code as "print-move-print.rs".

I offer these alternatives, one where we "move" the vec and print the new address twice:

// print-after-move.rs
#[inline(never)]
pub fn main() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec1;
    println!("{:p}", &vec2);
    println!("{:p}", &vec2);
}

And one where we keep the vec in the same variable and print its address twice:

// stay-printing.rs
#[inline(never)]
pub fn main() {
    let vec2 = vec![1, 2, 3];
    println!("{:p}", &vec2);
    println!("{:p}", &vec2);
}

The last is essentially the "optimized form" that you would want. Note the diff views, that show that the two alternatives I offer are considered equivalent.

It may be hard to follow all the assembly, but note that your version has this string:

        sub     rsp, 128

My versions have this string:

        sub     rsp, 96

That's the size of the stack allocation for locals... and is 32 bytes! ...or the size of a Vec and a reference. I believe your attempt to inspect the address is actually what is producing the deoptimization. I have advocated quite strongly for optimizing the resulting assembly output of Rust code based on move semantics and declaring UB any programs that try to futz with obtaining pointers by strange means, yet it is still not clear to me that this is a desirable optimization after someone prints the address?

from rust.

Jerrody avatar Jerrody commented on June 20, 2024

Behaviour is the same in debug and release.

Code

fn main() {
    let vec1 = vec![1, 2, 3];
    println!("{:p}", &vec1);
    let vec2 = vec1;
    println!("{:p}", &vec2);
}

Output

0x7ffe2bd06e90 0x7ffe2bd06e70
This is just waste of CPU time on performing this machinery, where basically changes only identifier.

Do you have a version of this that does not require printing the pointer values?

Consider this Godbolt: https://rust.godbolt.org/z/79vGhfcMv

I refer to your code as "print-move-print.rs".

I offer these alternatives, one where we "move" the vec and print the new address twice:

// print-after-move.rs
#[inline(never)]
pub fn main() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec1;
    println!("{:p}", &vec2);
    println!("{:p}", &vec2);
}

And one where we keep the vec in the same variable and print its address twice:

// stay-printing.rs
#[inline(never)]
pub fn main() {
    let vec2 = vec![1, 2, 3];
    println!("{:p}", &vec2);
    println!("{:p}", &vec2);
}

The last is essentially the "optimized form" that you would want. Note the diff views, that show that the two alternatives I offer are considered equivalent.

It may be hard to follow all the assembly, but note that your version has this string:

        sub     rsp, 128

My versions have this string:

        sub     rsp, 96

That's the size of the stack allocation for locals... and is 32 bytes! ...or the size of a Vec and a reference. I believe your attempt to inspect the address is actually what is producing the deoptimization. I have advocated quite strongly for optimizing the resulting assembly output of Rust code based on move semantics and declaring UB any programs that try to futz with obtaining pointers by strange means, yet it is still not clear to me that this is a desirable optimization after someone prints the address?

What I would expected in this situation is keeping same pointer to the Vec, instead Rust creates copy of that pointer that points to the Vec. I understand that this example is simple and trivial, but in big applications can happen many moves and this is, little, but unnecessary copy that CPU does, but in reality it can just do nothing and ownership isn't violating, just changes identifier.

from rust.

workingjubilee avatar workingjubilee commented on June 20, 2024

@Jerrody Please examine my examples again.

The optimization you describe already occurs, but not if you print out the address.

from rust.

Jerrody avatar Jerrody commented on June 20, 2024

@Jerrody Please examine my examples again.

The optimization you describe already occurs, but not if you print out the address.

Alright, another question, why it happens that if I print an address before move and after move then I get different address. I already understood that we get this optimization, but in my example, why it's different.

from rust.

the8472 avatar the8472 commented on June 20, 2024

What jublee is saying is that by trying to observe how the program behaves (printing the addresses) you're altering its behavior. If you don't print the addresses then multiple uses of vec do optimize.

Here's a simpler version with lots of moves: https://rust.godbolt.org/z/Y9xo64o57.
This does optimize, even at opt-level 0!

If we add something that takes a reference but does not leak its address bits then the moves still optimizes away: https://rust.godbolt.org/z/bM8vPc5Ea
The extra mov instrunctions in the assembly are just the length getting black-boxed, not the vec being moved.

I already understood that we get this optimization, but in my example, why it's different.

Printing the address means the address bits now become observable program behavior.
Behavior that is not observable¹ can be modified or eliminated by the optimizer under the as-if rule. Which means that not printing the address gives the optimizer more freedom.

¹according to the language specification, things like debuggers do not count

from rust.

workingjubilee avatar workingjubilee commented on June 20, 2024

The problem with your desired optimization, @Jerrody, is ultimately because of this program:

// pointer-escape-kinda.rs
use std::{error, ptr};
#[derive(Debug)]
struct MoveArray<T, const N: usize>([T; N]);

#[inline(never)]
pub fn main() -> Result<(), Box<dyn error::Error>> {
    let place_0 = MoveArray::<i32, 3>([1, 2, 3]);
    let ptr = ptr::addr_of!(place_0) as *mut i32;
    let place_1 = place_0;
    unsafe { ptr.write(5) };
    println!("{:?}", unsafe { (ptr as *mut [i32; 3]).read() });
    println!("{:?}", &place_1);
    Ok(())
}

Note that I do something that one might normally consider "impossible": mutate an immutable location. Indeed, if you run it via Tools -> Miri, Miri will complain about it being UB, currently. This being true makes some other semantics harder to implement, so whether this should be permitted is considered an open question in Rust's operational semantics:

It's not implausible, though, due to the fact you derive the pointer from &Vec<i32>, which has less questions than the ptr::addr_of! I just used.

from rust.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.