rust - 为什么在移动对象后,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);
}
使用标志编译代码后--release
,它在我的计算机上提供以下输出。
String occupies 24 bytes on the stack.
s at 0x7ffee3b011b0
r at 0x7ffee3b011c8
很明显,即使s
被移动,r
也不会重用堆栈上原本属于s
. 我认为重用移动对象的堆栈内存是安全的,但为什么 Rust 编译器不这样做呢?我错过了任何角落案例吗?
更新:如果我s
用大括号括起来,r
可以重用堆栈上的 24 字节块。
#[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);
}
上面的代码给出了下面的输出。
String occupies 24 bytes on the stack.
s at 0x7ffee2ca31f8
r at 0x7ffee2ca31f8
我认为大括号应该没有任何区别,因为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
更新 2:我想澄清我对这个问题的关注。我想知道提出的“堆栈重用优化”属于哪个类别。
- 这是无效的优化。在某些情况下,如果我们执行“优化”,编译的代码可能会失败。
- 这是一个有效的优化,但编译器(包括 rustc 前端和 llvm)不能执行它。
- 这是一个有效的优化,但暂时关闭,就像这样。
- 这是一个有效的优化,但被遗漏了。将来会添加。
解决方案
我的 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);
}
而不是consume_string
我使用std::mem::drop
which 专门用于简单地消费一个对象。这段代码的行为就像你的一样:
Obj occupies 8 bytes on the stack.
0x7ffe81a43fa0
0x7ffe81a43fa8
删除drop
不影响结果。
所以问题是为什么 rustc 在上线之前没有注意到它s
已经死了r
。正如您的第二个示例所示,包含s
在范围内将允许优化。
为什么这行得通?因为 Rust 语义规定对象在其作用域的末尾被删除。由于s
位于内部范围内,因此在范围退出之前将其删除。没有范围,在函数退出s
之前是活动的。main
为什么它在s
进入一个函数时不起作用,它应该在退出时被丢弃?s
可能是因为 rust在函数调用后没有正确地将所使用的内存位置标记为空闲。正如评论中提到的,实际上是 LLVM 处理了这种优化(据我所知称为“堆栈着色”),这意味着当内存不再使用时,rustc 必须正确地告诉它。显然,从您的上一个示例中, rustc 在范围退出时执行此操作,但显然不是在移动对象时执行此操作。
推荐阅读
- javascript - 当 this.state=X 时如何在 React 组件中运行 while 循环
- postgresql - PostgreSQL(全文搜索)与 ElasticSearch
- android - 为什么此警报对话框未在 backPressed 上显示?
- ruby-on-rails - ActiveAdmin 如何装饰关联的链接
- informatica - 自动生成 Web 服务参数
- swift - 拆分视图控制器偶尔会在 iOS 13 上的 iPhone 上显示详细视图
- spring-boot - 适合动态运行时配置的设计模式
- wso2 - Not able to find Logging option in wso2 management console in WSO2 version 3.0.0
- sql - 使用数组优化 SQL 查询
- python - 搁置 - 附加新值