在某一天移动对象的过程中,Rust能否优化位拷贝?
考虑一下这个片段在某一天移动对象的过程中,Rust能否优化位拷贝?,rust,move-semantics,llvm-codegen,Rust,Move Semantics,Llvm Codegen,考虑一下这个片段 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 地址不同的地
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
已被复制,正如编译器的move实现中所预期的那样。不幸的是,这可能会对性能产生不小的影响,因为dummy
是一个非常大的数组。这种影响会迫使人们选择通过引用传递参数,即使函数实际上在概念上“使用”了参数
由于Foo
不派生Copy
,对象o
被移动。由于锈蚀禁止访问移动的对象,是什么阻止bar
“重用”原始对象o
,迫使编译器生成一个可能昂贵的逐位拷贝?有没有一个根本性的困难,或者有一天我们会看到编译器优化掉这个按位复制吗?考虑到在Rust中(与C或C++不同),值的地址并不重要,从语言的角度来看,没有任何东西可以阻止省略复制
然而,现在rustc并没有优化任何东西:所有的优化都委托给LLVM,而且似乎您在这里遇到了LLVM优化器的一个限制(不清楚这个限制是由于LLVM接近C的语义还是仅仅是一个遗漏)
因此,有两种改进代码生成的方法:
- 教LLVM执行此优化(如果可能)
- 教rustc执行此优化(现在rustc已经有了MIR,优化过程将传递给它)
但现在,您可能只想避免在堆栈上分配如此大的对象,例如,您可以
Box
it。Rustc确实优化了移动。在本例中它没有这样做,可能是因为llvm没有内联条。这甚至可能是因为您试图观察指针值,而llvm不确定优化是否安全。我在没有打印:p
的情况下尝试了它,并改用test::black\u框,副本从程序集中消失。@Manishearth条
正在内联。LLVM在删除大型阵列的移动方面非常糟糕。NRVO
标记的问题与此相关:在这种情况下是否保证o
丢弃?鉴于它被移出到bar()
,那么o
内存的释放点是什么?说到MIR优化过程,第一个是简单的移动目标传播过程:。跟踪问题是。与其只是避免堆栈分配,不如假设移动将得到优化,如果没有优化,则只在以后装箱。在《铁锈》中,大多数时候你不应该考虑避免抄袭。@MichaelYounkin:我部分同意。问题是,在堆栈上复制几次大型对象很容易导致堆栈溢出,特别是在没有进行优化的调试目标中。如果缓冲区非常大,那么动态分配的成本应该比初始化缓冲区本身的成本要小得多。@MatthieuM在堆上分配它非常好,但根据我的经验,即使是writing Box::new(BigStruct::new())也会先在堆栈中分配BigStruct(在BigStruct::new中),然后在堆中复制它(在框中::新建)。还是我遗漏了什么?@Pierre Antoine:在调试中,是的,现在;这就是为什么如此受欢迎的原因。在发行版中,堆栈副本应该希望得到优化,但这可能会导致调试中的堆栈溢出,阻止您测试代码:(