为什么对象移动后Rust编译器不重用堆栈上的内存? [英] Why does the Rust compiler not reuse the memory on the stack after an object is moved?

查看:44
本文介绍了为什么对象移动后Rust编译器不重用堆栈上的内存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为,一旦移动了一个对象,该对象在堆栈上所占用的内存就可以用于其他目的.但是,下面的最小示例显示了相反的情况.

I thought that once an object is moved, the memory occupied by it on the stack can be reused for other purpose. However, the minimal example below shows the opposite.

#[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 标志编译代码后,它将在我的计算机上提供以下输出.

After compiling the code with --release flag, it gives the following output on my computer.

String occupies 24 bytes on the stack.
s at 0x7ffee3b011b0
r at 0x7ffee3b011c8

很明显,即使移动了 s r 也不会重用最初属于 s .我想重用已移动对象的堆栈内存是安全的,但是Rust编译器为什么不这样做呢?我是否想念任何特殊情况?

It is pretty clear that even if s is moved, r does not reuse the 24-byte chunk on the stack that originally belonged to s. I suppose that reusing the stack memory of a moved object is safe, but why does the Rust compiler not do it? Am I missing any corner case?

更新:如果我用大括号将 s 括起来,则 r 可以重用堆栈上的24字节块.

Update: If I enclose s by curly brackets, r can reuse the 24-byte chunk on the stack.

#[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);
}

上面的代码给出下面的输出.

The code above gives the output below.

String occupies 24 bytes on the stack.
s at 0x7ffee2ca31f8
r at 0x7ffee2ca31f8

我认为大括号应该没有任何区别,因为 s 的生​​存期在调用 comsume_string(s)后结束,并且其放置处理程序在 comsume_string().为什么添加大括号可以实现优化?

I thought that the curly brackets should not make any difference, because the lifetime of s ends after calling comsume_string(s) and its drop handler is called within comsume_string(). Why does adding the curly brackets enable the optimization?

下面给出了我正在使用的Rust编译器的版本.

The version of the Rust compiler I am using is given below.

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:我想澄清我对这个问题的关注.我想知道所提出的堆栈重用优化".属于哪个类别.

Update 2: I would like to clarify my focus of this question. I want to know the proposed "stack reuse optimization" lies in which category.

  1. 这是无效的优化.在某些情况下,如果我们执行优化",则编译后的代码可能会失败.
  2. 这是有效的优化,但编译器(包括rustc前端和llvm)均无法执行.
  3. 这是一个有效的优化,但暂时关闭,如

    推荐答案

    我的TLDR结论:错失了优化机会.

    My TLDR conclusion: A missed optimization opportunity.

    因此,我要做的第一件事是调查您的 consume_string 函数是否真正起作用.为此,我创建了以下(更多)最小示例:

    So the first thing I did was look into whether your consume_string function actually makes a difference. To do this I created the following (a bit more) minimal example:

    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);
    }
    

    我使用 std :: mem :: drop 而不是 consume_string ,它专门用于简单地消费一个对象.此代码的行为与您的一样:

    Instead of consume_string I use std::mem::drop which is dedicated to simply consuming an object. This code behaves just like yours:

    Obj occupies 8 bytes on the stack.
    0x7ffe81a43fa0
    0x7ffe81a43fa8
    

    删除 drop 不会影响结果.

    那么问题是,为什么rustc在 r 上线之前没有注意到 s 已经死了.如您的第二个示例所示,将 s 包含在范围内将允许优化.

    So the question is then why rustc doesn't notice that s is dead before r goes live. As your second example shows, enclosing s in a scope will allow the optimization.

    为什么这样做?因为Rust语义规定对象在其作用域的末尾被删除.由于 s 在内部作用域中,因此在作用域退出之前将其删除.没有作用域, s 会一直存在,直到 main 函数退出.

    Why does this work? Because the Rust semantics dictate that an object is dropped at the end of its scope. Since s is in an inner scope, it is dropped before the scope exits. Without the scope, s is alive until the main function exits.

    为什么在将 s 移至函数中并在退出时将其删除的地方不起作用?可能是因为rust在函数调用后没有正确地将 s 使用的内存位置标记为空闲.正如评论中提到的那样,实际上是LLVM处理此优化(称为堆栈着色" 据我所知),这意味着rustc必须在不再使用内存时正确告诉它.显然,在您的上一个示例中,rustc是在作用域退出时执行的,但显然不是在对象移动时执行的.

    Why doesn't it work when moving s into a function, where it should be dropped on exit? Probably because rust doesn't correctly flag the memory location used by s as free after the function call. As has been mentioned in the comments, it is LLVM that actually handles this optimization (called 'Stack Coloring' as far as I can tell), which means rustc must correctly tell it when the memory is no longer in use. Clearly, from your last example, rustc does it on scope exit, but apparently not when an object is moved.

    这篇关于为什么对象移动后Rust编译器不重用堆栈上的内存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆