rust - 调用函数时移动所有权是否会复制 `self` 结构?

标签 rust compiler-optimization

在我下面的示例中,cons.push(...) 是否复制了 self 参数?

或者 rustc 是否足够智能,可以意识到来自 #a#b 行的值始终可以使用相同的堆栈空间,并且不需要复制(明显的 i32 副本除外)?

换句话说,在移动所有权时,调用 Cons.push(self, ...) 是否总是创建 self 的副本?还是 self 结构始终留在堆栈中?

对文档的引用将不胜感激。

#[derive(Debug)]
struct Cons<T, U>(T, U);

impl<T, U> Cons<T, U> {
    fn push<V>(self, value: V) -> Cons<Self, V> {
        Cons(self, value)
    }
}

fn main() {
    let cons = Cons(1, 2); // #a
    let cons = cons.push(3); // #b
    println!("{:?}", cons); // #c
}

我上面例子中的含义是,每次我们添加像 #b 这样的行时,push(...) 函数的调用成本是否会增加O(n^2) 的速率(如果 self 每次都被复制)或者以 O(n) 的速率(如果 self 留在原地)。

我尝试实现 Drop 特性并注意到 #a#b 都被丢弃了 after #c。对我来说,这似乎表明 self 在此示例中保持不变,但我不是 100%。

最佳答案

总的来说,相信编译器! Rust + LLVM 是一个非常强大的组合,通常会产生出奇高效的代码。随着时间的推移,它会改进得更多。

In other words, does a call to Cons.push(self, ...) always create a copy of self as ownership is being moved? Or does the self struct always stay in place on the stack?

self不能留在原地,因为 push 返回的新值方法的类型为 Cons<Self, V> ,它本质上是一个元组 SelfV .虽然tuples don't have any memory layout guarantees ,我坚信他们的元素不能任意分散在内存中。因此,selfvalue两者都必须移入新结构。

以上段落假设self在调用 push 之前被牢牢地放在堆栈上.编译器实际上有足够的信息知道它应该为最终结构保留足够的空间。特别是对于函数内联,这很可能成为一种优化。

The implication in my example above is whether or not the push(...) function grows more expensive to call each time we add a line like #b at the rate of O(n^2) (if self is copied each time) or at the rate of O(n) (if self stays in place).

考虑两个函数 ( playground ):

pub fn push_int(cons: Cons<i32, i32>, x: i32) -> Cons<Cons<i32, i32>, i32> {
    cons.push(x)
}

pub fn push_int_again(
    cons: Cons<Cons<i32, i32>, i32>,
    x: i32,
) -> Cons<Cons<Cons<i32, i32>, i32>, i32> {
    cons.push(x)
}

push_int将第三个元素添加到 Conspush_int_again添加第四个元素。

push_int在 Release模式下编译为以下程序集:

movq    %rdi, %rax
movl    %esi, (%rdi)
movl    %edx, 4(%rdi)
movl    %ecx, 8(%rdi)
retq

push_int_again编译为:

movq    %rdi, %rax
movl    8(%rsi), %ecx
movl    %ecx, 8(%rdi)
movq    (%rsi), %rcx
movq    %rcx, (%rdi)
movl    %edx, 12(%rdi)
retq

您不需要了解汇编就可以看出压入第四个元素比压入第三个元素需要更多的指令。

请注意,此观察是针对这些功能单独进行的。像cons.push(x).push(y).push(...)这样的电话是内联的,并且程序集随着每次推送一条指令而线性增长。

关于rust - 调用函数时移动所有权是否会复制 `self` 结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57011967/

相关文章:

json - 是否可以使用 serde_json 反序列化部分 JSON 对象?

rust - 由于类型不匹配,对 Vec<Result<T, E>> 进行分区失败

c - GCC 编译器选项 : which set of enabled options is correct?

c++ - 使用 g++ 和 CMake 损坏的配置文件信息

rust - 如何在 Rust 中初始化 sigset_t 或其他用作 "out parameters"的变量?

concurrency - Futures : Rust vs runtime-based languages 的完成与就绪方法

rust - headless SDL 事件处理

c - 使用Cython作为C包装器

c++ - 虚函数调用的编译器优化

python - 与 Python 相比,Tensorflow C++ API 速度较慢