【问题标题】:Does Rust allocate on the stack to initialize an Option reference with Some?Rust 是否在堆栈上分配以使用 Some 初始化选项引用?
【发布时间】:2021-07-09 18:42:25
【问题描述】:

我一直在受限的嵌入式环境中使用 Rust(在 STM32F303 MCU 上),我注意到我的一些函数分配了出乎意料的大量堆栈空间。 在这种环境中,我没有分配器,需要在堆栈上分配大型、可变、静态的数据结构。 最终,我发现某些函数意外地在堆栈上分配空间,并导致我的内存受限堆栈溢出。

我希望了解以下mutate 函数需要在堆栈上分配多少内存。 我一直在这个网站上寻找这个问题的答案,但似乎没有人为这个更普遍的问题提供答案。 我知道对于下面的这个代码块,我可以查看 LLVM/程序集以查看分配的位置,但我试图了解如何预测何时为一般类问题进行堆栈分配 - 独立于编译器选项和优化器。 以下问题是一个玩具示例,它模仿了我在嵌入式 Rust 程序中使用(并且遇到问题)的模式。

问题:下面的mutate需要在栈上分配多少内存?

struct Parent {
    data: Option<[u32; 1024]>,
}

impl Parent {
    pub fn new() -> Self {
        Parent { data: None }
    }

    // Ideally, this function should allocate negligible memory on the stack
    pub fn mutate(&mut self) {
        let arr = [0u32; 1024];
        self.data = Some(arr);
        for i in 0..self.data.unwrap().len() {
            self.data.unwrap()[i] = i as u32;
        }
    }
}

fn main() {
    let mut it = Parent::new();
    it.mutate();
}

注意事项

  • 其他帖子似乎表明这种行为取决于优化器。如果是这种情况,有没有办法可以重写上面的代码,以便代码的读者可以清楚地看到每个函数的堆栈分配大小?
  • let 关键字(mutate 函数的第一行)对大数组是否在栈上分配有影响吗?
  • 我确信我可以使用unsafe rust 以确保该函数不需要进行任何分配/memcpys(就像我在 C 中所做的那样)。在不使用unsafe 的情况下,有没有更好的方法来做到这一点?

在此先感谢您对此问题的任何帮助。

【问题讨论】:

  • 如果您认为 Option 可以避免使用错误的数组大小。我不明白你的期望......这看起来像是 xy 问题的变化。
  • play.rust-lang.org/… 有什么问题?
  • 我希望你意识到整个 for 循环什么都不做,因为每个 unwrap() 都会复制整个数组。
  • 感谢您的回复。我知道这里的选项将分配超过数组的大小,并且它不能用于节省空间。在我的程序中,Parent 结构可能处于多种状态,其中一种状态是数组尚未准备好使用,另一种状态是数组已由成员函数初始化为 Some,并准备好在计算。
  • 改变数组的元素不需要复制任何东西(除了你改变它们的单个值)。您正在复制数组 而不是 对其进行变异。你基本上有this problem 除了[u32; 1024]Copy 所以不存在所有权问题。如果你在不使用unwrapit's initialization where the extraneous copy happens 的情况下改变数组的元素,这很难摆脱,但对于 LLVM 应该是相当容易处理的。

标签: memory rust embedded stack-overflow option-type


【解决方案1】:

问题:mutate 下面需要在栈上分配多少内存?

4kB (32 = 8 * 1024)?您实际上是在堆栈上创建一个本地数组,而 Rust 没有新位置,因此即使您没有在堆栈上创建一个本地数组,它基本上取决于它处理分配的优化器。

还请注意,您的 Parent 结构也必然总是占用 4kB,实际上它可能需要 4kB + 4 或 8 个字节(不确定 u32 数组的对齐要求是什么)作为数组的选项标签没有 rustc 可用于利基变体优化的无效值。

edit:哦,等等,不,您还使用 self.data.unwrap() 调用将数组 back 复制到函数中,所以还有 2 个,所以至少 12kB。事实上,将其插入 Compiler Explorer 会告诉我:

example::Parent::mutate: mov eax, 28856

所以那是 28k,不太确定额外费用来自哪里。

无论如何激活-O,看起来一切都被优化了,所以……幸运吗?:

example::Parent::mutate:
        push    rax
        mov     dword ptr [rdi], 1
        add     rdi, 4
        mov     edx, 4096
        xor     esi, esi
        call    qword ptr [rip + memset@GOTPCREL]
        pop     rax
        ret

【讨论】:

  • 感谢您的回复。我正在寻找重写 mutate 函数,以便它不需要在堆栈上分配内存 - 而是使用已经在父结构中分配的内存。有没有办法编写这个函数,使其内存占用可以忽略不计——独立于优化器?
  • @KevinKellar 从结构中删除Option(没有间接它实际上不会节省任何内存)然后在成员上就地工作?如果您坚持使用Option 成员,那么使用if let Some(a) = self.data.as_mut() { iterate and manipulate array here } 之类的东西将有助于获得对选项内数组的引用(指针)(如果有的话),而不是将数据移入和移出。
  • 我没有使用选项来节省内存。在我的程序中,Parent 结构需要能够在不初始化 Option 内的数据的情况下构建,因此在构造函数中将其设置为 None。
  • 如果父结构的字段是 Option,我怎样才能将 self.data 初始化为 Some 而不必在 mutate 的堆栈帧上为整个 Otherstruct 分配空间?在 C 语言中,函数可以获取指向 Option 内的单元化内存的指针,并使用该指针改变内存。但是在 rust 中,我可以引用 Option 的 Some 变体,但未初始化 Some 中的数据吗?
  • 我正在寻找一种方法来重写 mutate 函数以独立于编译器/优化器设置分配内存。
猜你喜欢
  • 1970-01-01
  • 2021-06-22
  • 2017-05-27
  • 1970-01-01
  • 2014-10-11
  • 2020-10-03
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
相关资源
最近更新 更多