当堆栈分配的值被装箱时会发生什么?
What happens when a stack-allocated value is boxed?
如果我们有一个已经在堆栈上分配的值,装箱是否会将其复制到堆中然后转移所有权(这就是它在 .NET 中的工作方式,除了两个副本都将保持活动状态)?还是编译器 "smart" 足以从一开始就直接在堆上分配它?
struct Foo {
x: i32,
}
fn main() {
// a is allocated on stack?
let a = Foo { x: 1 };
// if a is not used, it will be optimized out
println!("{}", a.x);
// what happens here? will the stack allocated structure
// be moved to heap? or was it originally allocated on heap?
let b = Box::new(a);
}
我不是汇编专家,但看起来它实际上是在堆栈上分配然后移动的:http://pastebin.com/8PzsgTJ1。但是我需要一个真正知道发生了什么的人的确认。
如 Rust 官方文档 here 中所述,Box<T>::new(x: T)
在堆上分配内存,然后 将参数移动 到该内存中。在 let b = Box::new(a)
之后访问 a
是编译时错误。
按照您的描述进行这种优化会很奇怪。例如,在这段代码中:
let a = Foo { x: 1 };
// operation that observes a
let b = Box::new(a);
// operation that observes b
&a
和 &b
将 相等 ,这会令人惊讶。然而,如果你做了类似的事情,但没有观察到 a
:
#[inline(never)]
fn frobnotz() -> Box<Foo> {
let a = Foo { x: 1 };
Box::new(a)
}
你可以see via the LLVM IR这个案例是优化的:
define internal fastcc noalias dereferenceable(4) %Foo* @_ZN8frobnotz20h3dca7bc0ee8400bciaaE() unnamed_addr #0 {
entry-block:
%0 = tail call i8* @je_mallocx(i64 4, i32 0)
%1 = icmp eq i8* %0, null
br i1 %1, label %then-block-106-.i.i, label %"_ZN5boxed12Box$LT$T$GTnew20h2665038481379993400E.exit"
then-block-106-.i.i: ; preds = %entry-block
tail call void @_ZN3oom20he7076b57c17ed7c6HYaE()
unreachable
"_ZN5boxed12Box$LT$T$GTnew20h2665038481379993400E.exit": ; preds = %entry-block
%2 = bitcast i8* %0 to %Foo*
%x.sroa.0.0..sroa_idx.i = bitcast i8* %0 to i32*
store i32 1, i32* %x.sroa.0.0..sroa_idx.i, align 4
ret %Foo* %2
}
同样的,你可以return栈上的struct然后装箱,还是会just be the one allocation:
You may think that this gives us terrible performance: return a value and then immediately box it up ?! Isn't this pattern the worst of both worlds? Rust is smarter than that. There is no copy in this code. main allocates enough room for the box, passes a pointer to that memory into foo as x, and then foo writes the value straight into the Box.
如果我们有一个已经在堆栈上分配的值,装箱是否会将其复制到堆中然后转移所有权(这就是它在 .NET 中的工作方式,除了两个副本都将保持活动状态)?还是编译器 "smart" 足以从一开始就直接在堆上分配它?
struct Foo {
x: i32,
}
fn main() {
// a is allocated on stack?
let a = Foo { x: 1 };
// if a is not used, it will be optimized out
println!("{}", a.x);
// what happens here? will the stack allocated structure
// be moved to heap? or was it originally allocated on heap?
let b = Box::new(a);
}
我不是汇编专家,但看起来它实际上是在堆栈上分配然后移动的:http://pastebin.com/8PzsgTJ1。但是我需要一个真正知道发生了什么的人的确认。
如 Rust 官方文档 here 中所述,Box<T>::new(x: T)
在堆上分配内存,然后 将参数移动 到该内存中。在 let b = Box::new(a)
之后访问 a
是编译时错误。
按照您的描述进行这种优化会很奇怪。例如,在这段代码中:
let a = Foo { x: 1 };
// operation that observes a
let b = Box::new(a);
// operation that observes b
&a
和 &b
将 相等 ,这会令人惊讶。然而,如果你做了类似的事情,但没有观察到 a
:
#[inline(never)]
fn frobnotz() -> Box<Foo> {
let a = Foo { x: 1 };
Box::new(a)
}
你可以see via the LLVM IR这个案例是优化的:
define internal fastcc noalias dereferenceable(4) %Foo* @_ZN8frobnotz20h3dca7bc0ee8400bciaaE() unnamed_addr #0 {
entry-block:
%0 = tail call i8* @je_mallocx(i64 4, i32 0)
%1 = icmp eq i8* %0, null
br i1 %1, label %then-block-106-.i.i, label %"_ZN5boxed12Box$LT$T$GTnew20h2665038481379993400E.exit"
then-block-106-.i.i: ; preds = %entry-block
tail call void @_ZN3oom20he7076b57c17ed7c6HYaE()
unreachable
"_ZN5boxed12Box$LT$T$GTnew20h2665038481379993400E.exit": ; preds = %entry-block
%2 = bitcast i8* %0 to %Foo*
%x.sroa.0.0..sroa_idx.i = bitcast i8* %0 to i32*
store i32 1, i32* %x.sroa.0.0..sroa_idx.i, align 4
ret %Foo* %2
}
同样的,你可以return栈上的struct然后装箱,还是会just be the one allocation:
You may think that this gives us terrible performance: return a value and then immediately box it up ?! Isn't this pattern the worst of both worlds? Rust is smarter than that. There is no copy in this code. main allocates enough room for the box, passes a pointer to that memory into foo as x, and then foo writes the value straight into the Box.