Zhi*_* Ma 14 llvm compiler-optimization rust
我认为一旦一个对象被移动,它在堆栈上占用的内存可以被重用于其他目的。但是,下面的最小示例显示了相反的情况。
#[inline(never)]
fn consume_string(s: String) {
drop(s);
}
fn main() {
println!(
"String occupies {} bytes on the stack.",
std::mem::size_of::<String>()
);
let s = String::from("hello");
println!("s at {:p}", &s);
consume_string(s);
let r = String::from("world");
println!("r at {:p}", &r);
consume_string(r);
}
Run Code Online (Sandbox Code Playgroud)
使用--release
标志编译代码后,它在我的计算机上提供以下输出。
String occupies 24 bytes on the stack.
s at 0x7ffee3b011b0
r at 0x7ffee3b011c8
Run Code Online (Sandbox Code Playgroud)
很明显,即使s
被移动,r
也不会重用最初属于 的堆栈上的 24 字节块s
。我认为重用移动对象的堆栈内存是安全的,但为什么 Rust 编译器不这样做呢?我错过了任何角落案例吗?
更新:如果我s
用大括号括起来,r
可以重用堆栈上的 24 字节块。
String occupies 24 bytes on the stack.
s at 0x7ffee3b011b0
r at 0x7ffee3b011c8
Run Code Online (Sandbox Code Playgroud)
上面的代码给出了下面的输出。
String occupies 24 bytes on the stack.
s at 0x7ffee2ca31f8
r at 0x7ffee2ca31f8
Run Code Online (Sandbox Code Playgroud)
我认为大括号应该没有任何区别,因为s
调用后结束的生命周期comsume_string(s)
及其放置处理程序在comsume_string()
. 为什么添加大括号可以优化?
下面给出了我正在使用的 Rust 编译器的版本。
rustc 1.54.0-nightly (5c0292654 2021-05-11)
binary: rustc
commit-hash: 5c029265465301fe9cb3960ce2a5da6c99b8dcf2
commit-date: 2021-05-11
host: x86_64-apple-darwin
release: 1.54.0-nightly
LLVM version: 12.0.1
Run Code Online (Sandbox Code Playgroud)
更新 2:我想澄清我对这个问题的关注。我想知道建议的“堆栈重用优化”属于哪个类别。
我的 TLDR 结论:错过了优化机会。
所以我做的第一件事就是看看你的consume_string
功能是否真的有所作为。为此,我创建了以下(更多)最小示例:
struct Obj([u8; 8]);
fn main()
{
println!(
"Obj occupies {} bytes on the stack.",
std::mem::size_of::<Obj>()
);
let s = Obj([1,2,3,4,5,6,7,8]);
println!("{:p}", &s);
std::mem::drop(s);
let r = Obj([11,12,13,14,15,16,17,18]);
println!("{:p}", &r);
std::mem::drop(r);
}
Run Code Online (Sandbox Code Playgroud)
而不是consume_string
我使用std::mem::drop
which 专门用于简单地消费一个对象。这段代码的行为和你的一样:
Obj occupies 8 bytes on the stack.
0x7ffe81a43fa0
0x7ffe81a43fa8
Run Code Online (Sandbox Code Playgroud)
删除drop
不影响结果。
所以问题是为什么 rustcs
在上r
线之前没有注意到它已经死了。正如您的第二个示例所示,包含s
在范围内将允许优化。
为什么这样做?因为 Rust 语义规定一个对象在其范围的末尾被删除。由于s
在内部作用域中,因此在作用域退出之前将其删除。如果没有作用域,s
则在main
函数退出之前一直处于活动状态。
为什么它在s
进入一个函数时不起作用,它应该在退出时删除?可能是因为 rust 没有正确地s
将函数调用后使用的内存位置标记为 free。正如评论中提到的,实际上是 LLVM 处理这种优化(据我所知,称为“堆栈着色”),这意味着 rustc 必须正确地告诉它何时不再使用内存。显然,从您的最后一个示例中, rustc 在范围退出时执行此操作,但显然不是在移动对象时执行。