Rust能否在未来优化对象移动时的位复制过程?

29

考虑这段代码片段

struct Foo {
    dummy: [u8; 65536],
}

fn bar(foo: Foo) {
    println!("{:p}", &foo)
}

fn main() {
    let o = Foo { dummy: [42u8; 65536] };
    println!("{:p}", &o);
    bar(o);
}

程序的典型结果如下:

0x7fffc1239890
0x7fffc1229890

地址不同。

显然,大数组dummy已经被复制,正如编译器的移动实现所预期的那样。不幸的是,这可能会对性能产生非常重要的影响,因为dummy是一个非常大的数组。这种影响可能会迫使人们选择通过引用传递参数,即使函数在概念上实际上“消耗”了该参数。

由于Foo没有派生Copy,因此对象o被移动了。由于Rust禁止访问已移动的对象,是什么阻止了bar“重用”原始对象o,从而迫使编译器生成潜在昂贵的按位复制?这是否存在根本的难题,或者我们是否会看到编译器有一天优化掉这种按位复制?


22
Rustc会优化移动操作,但在这种情况下它没有这样做,可能是因为llvm没有内联bar函数。这甚至可能是因为您试图观察指针值,而llvm不确定是否安全进行优化。我尝试删除 :p 输出并改用test :: black_box,然后从汇编中消除了复制操作。 - Manishearth
@Manishearth bar正在被内联。LLVM在移除大数组的移动操作方面表现不佳。 - Veedrac
NRVO 标签的问题与此相关:https://github.com/rust-lang/rust/labels/A-mir-opt-nrvo - WiSaGaN
在这种情况下,o的释放是否有保证?考虑到它被移出到了bar()中,o内存会在什么时候释放? - Ilya Loskutov
1个回答

27
考虑到在Rust中(与C或C++不同)值的地址并不重要,因此从语言角度来看,没有阻止复制省略的内容。然而,当前的rustc没有进行任何优化:所有优化都委托给LLVM,并且在这里似乎遇到了LLVM优化器的限制(不清楚这种限制是由于LLVM接近C的语义还是仅仅是一种疏漏)。因此,有两种方法可以改进代码生成:
- 教会LLVM执行此优化(如果可能) - 教会rustc执行此优化(现在拥有MIR后,优化通道正在到来)
但是现在您可能只想避免在堆栈上分配这样大的对象,比如可以使用`Box`。

6
说到 MIR 优化通道,第一个是简单的目标传播通道:https://github.com/rust-lang/rust/pull/34693。跟踪问题在 https://github.com/rust-lang/rust/issues/32966。 - eddyb
1
不要仅仅避免堆栈分配,最好的做法是假设移动操作会被优化,只有在必要时才进行装箱。在 Rust 中,大多数情况下你不应该考虑尝试避免复制操作。 - Michael Younkin
@MichaelYounkin:我部分同意。问题在于,在堆栈上复制几次大对象很容易导致堆栈溢出,特别是在调试目标中,优化不会发生的情况下。如果缓冲区非常大,则动态分配的成本应该被初始化缓冲区本身的成本所淹没。 - Matthieu M.
1
@MatthieuM 在堆上分配内存是很好的,但根据我的经验,即使先编写Box::new(BigStruct::new()),也会在堆栈中分配BigStruct(在BigStruct::new中),然后将其复制到堆中(在Box::new中)。或者我错过了什么? - Pierre-Antoine
@Pierre-Antoine:目前在Debug模式下是这样的;这就是为什么placement new如此受欢迎。在Release模式下,堆栈复制应该会被优化掉,但这可能会导致在Debug模式下发生堆栈溢出,从而阻止您测试代码:( - Matthieu M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接