为什么 Rust 编译器在移动不可变值时会执行复制操作?

huangapple go评论74阅读模式
英文:

Why does the Rust compiler perform a copy when moving an immutable value?

问题

我的直觉对于移动和复制可能是错误的。我期望Rust编译器会将对不可变值的移动优化为无操作。由于该值是不可变的,在移动后我们可以安全地重用它。但是在Rust 1.65.0 on Godbolt上编译的汇编代码将该值复制到内存中的新位置。我正在研究的Rust代码:

pub fn f_int() {
    let x = 3;
    let y = x;
    println!("{}, {}", x, y);
}

使用-C opt-level=3选项得到的汇编代码:

    ; pub fn f_int() {
    sub     rsp, 88
    ; let x = 3;
    mov     dword ptr [rsp], 3
    ; let y = x;
    mov     dword ptr [rsp + 4], 3
    mov     rax, rsp
    ...

为什么let y = x;会导致mov dword ptr [rsp + 4], 3mov rax, rsp?为什么编译器在汇编中没有将y视为与x相同的变量?

这个问题看起来类似,但它涉及到了不可复制的字符串。我的问题是关于可复制的整数。看起来我描述的情况不是一个被忽视的优化机会,而是我理解上的一个基本错误。)

英文:

My intuition must be wrong about moves and copies. I would expect the Rust compiler optimize away moves of an immutable value as a no-op. Since the value is immutable, we can safely reuse it after the move. But Rust 1.65.0 on Godbolt compiles to assembly that copies the value to a new position in memory. The Rust code that I am studying:

pub fn f_int() {
    let x = 3;
    let y = x;
    println!("{}, {}", x, y);
}

The resulting assembly with -C opt-level=3:

    ; pub fn f_int() {
    sub     rsp, 88
    ; let x = 3;
    mov     dword ptr [rsp], 3
    ; let y = x;
    mov     dword ptr [rsp + 4], 3
    mov     rax, rsp
    ...

Why does let y = x; result in mov dword ptr [rsp + 4], 3 and mov rax, rsp? Why doesn't the compiler treat y as the same variable as x in the assembly?

(This question looks similar but it is about strings which are not Copy. My question is about integers which are Copy. It looks like what I am describing is not a missed optimization opportunity but a fundamental mistake in my understanding.)

答案1

得分: 4

如果你将示例更改如下
```rust
pub fn f_int() -> i32 {
    let x = 3;
    let y = x;
    // println!("{}, {}", x, y);
    x+y
}

优化会发生

example::f_int:
        mov     eax, 6
        ret

println!() (以及write!()...)会对其参数取引用,并使用这些引用提供给格式化机制。
可能,编译器推断出为一些(没有内联)的函数提供引用需要将数据存储在内存中以获得地址。
因为类型是Copy,语义意味着我们有两个不同的存储,否则,共享存储将会是对于一个移动操作的优化(而不是复制)。


<details>
<summary>英文:</summary>

If you change your example like this
```rust
pub fn f_int() -&gt; i32 {
    let x = 3;
    let y = x;
    // println!(&quot;{}, {}&quot;, x, y);
    x+y
}

the optimisation takes place

example::f_int:
        mov     eax, 6
        ret

The println!() macro (as well as write!()...) takes references on its parameters and provides the formatting machinery with these references.
Probably, the compiler deduces that providing some functions (that are not inlined) with references requires the data being stored somewhere in memory in order to have an address.
Because the type is Copy, the semantics implies that we have two distinct storages, otherwise, sharing the storage would have been an optimisation for a move operation (not a copy).

答案2

得分: 4

I would not call it a fundamental mistake in your understanding, but there are some interesting observations here.

First, println!() (and the formatting machinery in particular) is surprisingly hard to optimize, due to its design. So the fact that with println!() it was not optimized is not surprising.

Second, it is generally not obvious it is OK to perform this optimization, because it observably make the addresses equivalent. And println!() takes the address of the printed values (and passes them to an opaque function). In fact, Copy types are harder to justify than non-Copy types in that regard, because with Copy types the original variable may still be used after a move while with non-Copy types it is possible that not.

英文:

I would not call it a fundamental mistake in your understanding, but there are some interesting observations here.

First, println!() (and the formatting machinery in particular) is surprisingly hard to optimize, due to its design. So the fact that with println!() it was not optimized is not surprising.

Second, it is generally not obvious it is OK to perform this optimization, because it observably make the addresses equivalent. And println!() takes the address of the printed values (and passes them to an opaque function). In fact, Copy types are harder to justify than non-Copy types in that regard, because with Copy types the original variable may still be used after a move while with non-Copy types it is possible that not.

huangapple
  • 本文由 发表于 2023年2月19日 00:48:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75494841.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定