gpt4 book ai didi

rust - Rust 中的复制语义真的变成了内存中的副本吗?

转载 作者:行者123 更新时间:2023-11-29 08:01:26 27 4
gpt4 key购买 nike

假设我在 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。否则,第二次添加将是一个编译错误,因为 ab 在第一次添加时已经被移动到 add 函数中(我想想)。

问题是发出的程序集做了什么。当调用 add 函数时,是否将参数的两个副本复制到新的堆栈帧中,或者 Rust 编译器是否足够聪明,知道在这种情况下,没有必要进行该复制?

如果 Rust 编译器不够智能,并且实际上像 C++ 中带有按值传递参数的函数一样进行复制,那么在重要的情况下如何避免性能开销?

上下文是我正在实现一个矩阵类(只是为了学习),如果我有一个 100x100 矩阵,我真的不想每次尝试乘法或加法时都调用两个副本。

最佳答案

The question is what the emitted assembly does

无需猜测;你可以看看。让我们使用这段代码:

use std::ops::Add;

#[derive(Copy, Clone, Debug)]
struct Num(i32);

impl Add for Num {
type Output = Num;

fn add(self, rhs: Num) -> Num {
Num(self.0 + rhs.0)
}
}

#[inline(never)]
fn example() -> Num {
let a = Num(0);
let b = Num(1);
let c = a + b;
let d = a + b;
c + d
}

fn main() {
println!("{:?}", example());
}

将其粘贴到 Rust Playground 中,然后选择 Release 模式并查看 LLVM IR:

release mode LLVM IR

搜索结果以查看示例 函数的定义:

; playground::example
; Function Attrs: noinline norecurse nounwind nonlazybind readnone uwtable
define internal fastcc i32 @_ZN10playground7example17h60e923840d8c0cd0E() unnamed_addr #2 {
start:
ret i32 2
}

没错,这是在编译时进行了全面评估,并一直简化为一个简单的常量。现在的编译器非常好。

也许您想尝试一些不那么硬编码的东西?

#[inline(never)]
fn example(a: Num, b: Num) -> Num {
let c = a + b;
let d = a + b;
c + d
}

fn main() {
let something = std::env::args().count();
println!("{:?}", example(Num(something as i32), Num(1)));
}

生产

; playground::example
; Function Attrs: noinline norecurse nounwind nonlazybind readnone uwtable
define internal fastcc i32 @_ZN10playground7example17h73d4138fe5e9856fE(i32 %a) unnamed_addr #3 {
start:
%0 = shl i32 %a, 1
%1 = add i32 %0, 2
ret i32 %1
}

糟糕,编译器看到我们基本上是在做 (x + 1) * 2,所以它在这里做了一些棘手的优化以达到 2x + 2。让我们尝试更难的事情......

#[inline(never)]
fn example(a: Num, b: Num) -> Num {
a + b
}

fn main() {
let something = std::env::args().count() as i32;
let another = std::env::vars().count() as i32;
println!("{:?}", example(Num(something), Num(another)));
}

生产

; playground::example
; Function Attrs: noinline norecurse nounwind nonlazybind readnone uwtable
define internal fastcc i32 @_ZN10playground7example17h73d4138fe5e9856fE(i32 %a, i32 %b) unnamed_addr #3 {
start:
%0 = add i32 %b, %a
ret i32 %0
}

一个简单的add指令。

真正的收获是:

  1. 查看为您的案例生成的程序集。即使是看起来相似的代码也可能以不同的方式进行优化。
  2. 执行微观和宏观基准测试。您永远不知道代码将如何在大局中发挥作用。也许您的所有缓存都将被耗尽,但您的微基准测试将非常出色。

is the Rust compiler smart enough to know that in this case, it's not necessary to do that copying?

正如您刚才看到的,Rust 编译器加上 LLVM 非常智能。通常,当它知道不需要操作数时,可能删除副本。它是否适用于您的情况很难回答。

即使是这样,您也可能不希望通过堆栈传递大项目,因为它总是可能需要被复制。

请注意,您不必为值实现复制,您可以选择仅通过引用允许它:

impl<'a, 'b> Add<&'b Num> for &'a Num {
type Output = Num;

fn add(self, rhs: &'b Num) -> Num {
Num(self.0 + rhs.0)
}
}

事实上,您可能想要实现添加它们的两种方式,也许还需要实现值/引用的所有 4 种排列!

关于rust - Rust 中的复制语义真的变成了内存中的副本吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32663478/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com