使用Rust中的更新语法创建新结构的运行时成本

时间:2018-01-11 17:31:56

标签: struct rust compiler-optimization

Rust书籍解释了如何使用struct update syntax创建一个只更新了几个字段的结构副本。

let mut point = Point3d { x: 0, y: 0, z: 0 };
point = Point3d { y: 1, .. point };

它也不必是相同的结构,在创建新结构时可以使用这种语法。

let origin = Point3d { x: 0, y: 0, z: 0 };
let point = Point3d { y: 1, .. origin };

我的问题是关于第二个例子。

  1. 如果永远不再使用origin点,编译器是否仍会使(几乎)所有字段的副本(如果字段很大)或者只更新更新字段到位并等同于第一个示例?

  2. 如果它到位更新:编译器如何确定它是否可以覆盖origin值?

3 个答案:

答案 0 :(得分:5)

要回答诸如“编译器是否优化此问题?”之类的问题,我建议您查看反汇编。如果我在编译器资源管理器上编译并优化(-O)以下内容:

pub struct P {
    x: i32,
    y: i32,
}

pub fn f() -> P {
    let mut point = P { x: 43, y: 12 };
    point = P { y: 1, ..point };
    point
}

我明白了:

push rbp
mov rbp, rsp
movabs rax, 4294967339
pop rbp
ret

如果你知道一点装配,你可以看到没有创建或复制的中间变量。您可以为返回值的初始化添加额外的步骤,输出程序集不应更改。

如果删除优化(我不会在此处显示程序集),您可以在程序集中看到编译器生成第一个Point { x: 43, y: 12 },然后通过复制x并初始化{{来创建一个新的y。 1}}到1。

总结一下:

  • 编译器生成您编写的逻辑:创建一个点,然后通过移动(即复制简单类型,如i32)从第一个数据中创建一些数据来创建另一个点。
  • 优化程序检测到不需要第一个点并将其优化掉。

答案 1 :(得分:1)

请注意,您正在链接到Rust书籍的第一个版本,特别是此时几乎<2年以上的版本。您的链接是1.6.0版的文档,但1.23.0是当前版本。 second edition of the book has different phrasing

  

Rust书籍解释了如何使用struct update语法来创建struct

的副本

更新语法没什么特别之处。此示例代码仅创建副本,因为结构成员(在本例中为i32)都实现了Copy

  

如果原点永远不会再次使用,编译器是否仍会复制[...]字段

这完全取决于结构的值是否实现Copy

#[derive(Debug)]
struct Coord;

#[derive(Debug)]
struct Point3d { x: Coord, y: Coord, z: Coord }

fn main() {
    let origin = Point3d { x: Coord, y: Coord, z: Coord };
    let point = Point3d { y: Coord, ..origin };

    println!("{:?}", origin);
}
error[E0382]: use of partially moved value: `origin`
  --> src/main.rs:15:22
   |
13 |     let point = Point3d { y: Coord, ..origin };
   |                                       ------ value moved here
14 |     
15 |     println!("{:?}", origin);
   |                      ^^^^^^ value used here after move
   |
   = note: move occurs because `origin.x` has type `Coord`, which does not implement the `Copy` trait

除非实施Copy,否则将移动值。 Copy表示编译器可以随意复制类型的位:它没有副作用,这样做很有效,而且语义正确。

如果某个值被移动(即未实现Copy),则之后您将无法使用它。

  

它也不必是相同的结构,在创建新结构时可以使用这种语法。

在这两种情况下都在制作一个新结构。碰巧的是,在第一种情况下,你碰巧有一个可变的绑定,你将它重新放入。它实际上相当于:

let point = Point3d { x: 0, y: 0, z: 0 };
let point = Point3d { y: 1, ..point };

答案 2 :(得分:0)

<强> YMMV 1

一般来说,优化器的结果没有确定性。语言的语义可能会放置一些算法边界,但是,是否出现副本,单个副本或三个副本的程度取决于具体情况。

<强>语义

Rust 默认移动,因此如果源代码中没有.clone(),则不会发生数据的深层复制。

另一方面,按位副本的数量未指定,可能会有所不同。

<强>优化

优化编译器(从那时起,优化器)在as-if规则下运行。每种语言都指定一组可观察行为,编译器必须生成与天真程序具有相同可观察行为的代码。可观察行为通常包括I / O,但忽略CPU周期和内存访问。

优化器有各种各样的技巧:

  • 它不需要实现结构,它只需操作字段,
  • 它不需要复制结构或字段,如果两个变量引用相同的值,则需要存在单个值,
  • 它不需要实现永远不会再读取的值,
  • 它不需要将可以保存到寄存器中的内容,
  • ...

那么,你的案件会发生什么?的没有

由于您不是使用 Point,因此它们不一定存在。

1 您的里程可能会发生变化,这是一个口语表达,意味着您的体验可能与其他人的体验不同,一般情况是因为您的情况略有不同。