假设我在Rust中有以下结构:
struct Num {
pub num: i32;
}
impl Num {
pub fn new(x: i32) -> Num {
Num { num: x }
}
}
impl Clone for Num {
fn clone(&self) -> Num {
Num { num: self.num }
}
}
impl Copy for Num { }
impl Add<Num> for Num {
type Output = Num;
fn add(self, rhs: Num) -> Num {
Num { num: self.num + rhs.num }
}
}
然后我有以下代码片段:
let a = Num::new(0);
let b = Num::new(1);
let c = a + b;
let d = a + b;
这是有效的,因为 Num
被标记为 Copy
. 否则,第二次添加将是编译错误,因为在第一次添加期间 a
和 b
已经被移动到 add
函数中(我认为) .
问题是发射的组件的作用 . 当调用 add
函数时,是否有两个参数副本进入新的堆栈框架,或者Rust编译器是否足够聪明,知道在这种情况下,没有必要进行复制?
如果Rust编译器不够智能,并且实际上像C语言中的值传递的函数一样进行复制,那么在重要的情况下如何避免性能开销?
上下文是我正在实现一个矩阵类(只是为了学习),如果我有一个100x100矩阵,我真的不想每次尝试进行乘法或加法时调用两个副本 .
2 回答
没有必要猜测;你可以看看 . 我们来使用这段代码:
将其粘贴到Rust Playground中,然后选择Release模式并查看LLVM IR:
搜索结果以查看
example
函数的定义:这是正确的,这在编译时完全和完全评估,并简化为一个简单的常量 . 编译器现在很不错 .
也许你想尝试一些不像硬编码的东西?
产生
哎呀,编译器看到我们基本上做了(x 1)* 2,所以它在这里进行了一些棘手的优化以获得2x 2.让我们更努力地尝试一下......
产生
一个简单的
add
指令 .真正的结果是:
查看为您的案例生成的程序集 . 即使是看似相似的代码也可能有不同的优
执行微观和宏观基准测试 . 你永远不知道代码将如何在大局中发挥作用 . 也许您的所有缓存都会被烧毁,但您的微基准测试将会非常出色 .
如您所见,Rust编译器 plus LLVM非常智能 . 通常,当知道不需要操作数时,可以删除副本 . 是否适用于您的情况很难回答 .
即使它确实如此,您可能也不希望通过堆栈传递大型项目,因为它总是可能需要复制 .
请注意,您不必为该值实现副本,您可以选择仅通过引用允许它:
实际上,您可能希望实现添加它们的两种方式,也可能是所有4种值/引用的排列!
Rust的隐式副本(来自移动或实际的
Copy
类型)的 All 是 shallowmemcpy
. 如果堆分配,则只复制指针等 . 与C不同,按值传递矢量只会复制三个指针大小的值 .要复制堆内存,必须通过调用
.clone()
,使用#[derive(Clone)]
或impl Clone
实现显式复制 .I've talked in more detail about this elsewhere.
Shepmaster指出,浅拷贝经常被编译器弄乱 - 通常只有堆内存和大量堆栈值会导致问题 .