首页 文章

线程'<main>'在创建大型数组时溢出了它的堆栈

提问于
浏览
2

static 变量 A_INTERSECTS_A 从以下代码返回错误 . 这段代码应返回一个大的1356x1356二维数组 bool .

use lazy_static::lazy_static; // 1.2.0

#[derive(Debug, Copy, Clone, Default)]
pub struct A {
    pub field_a: [B; 2],
    pub ordinal: i32,
}

#[derive(Debug, Copy, Clone, Default)]
pub struct B {
    pub ordinal: i32,
}

pub const A_COUNT: i32 = 1356;

lazy_static! {
    pub static ref A_VALUES: [A; A_COUNT as usize] = { [A::default(); A_COUNT as usize] };

    pub static ref A_INTERSECTS_A: [[bool; A_COUNT as usize]; A_COUNT as usize] = {
        let mut result = [[false; A_COUNT as usize]; A_COUNT as usize];

        for item_one in A_VALUES.iter() {
            for item_two in A_VALUES.iter() {
                if item_one.field_a[0].ordinal == item_two.field_a[0].ordinal
                    || item_one.field_a[0].ordinal == item_two.field_a[1].ordinal
                    || item_one.field_a[1].ordinal == item_two.field_a[0].ordinal
                    || item_one.field_a[1].ordinal == item_two.field_a[1].ordinal
                {
                    result[item_one.ordinal as usize][item_two.ordinal as usize] = true;
                }
            }
        }
        result
    };
}

fn main() {
    A_INTERSECTS_A[1][1];
}

我已经看到有人通过在大型列表中为结构实现 Drop 来处理这个问题,但是我的列表中没有任何结构,你不能为bool实现它 .

如果我将 A_INTERSECTS_A: [[bool; A_COUNT as usize]; A_COUNT as usize] 更改为 A_INTERSECTS_A: Box<Vec<Vec<bool>>> 代码工作正常,但我真的想在这里使用数组 .

1 回答

  • 7

    这里的问题几乎肯定是当 A_INTERSECTS_A 的初始化代码运行时放置在堆栈上的巨大的 result 数组 . 它是13562≈1.8MB,与堆栈的大小具有相似的数量级 . 实际上,它比Windows更大' default size of 1 MB (and I suspect you are on Windows, given you' ve得到了错误信息) .

    这里的解决方案是通过将堆栈大小移动到堆来减少堆栈大小,例如,使用 Vec (当您指示工作时)或使用 Box . 这将带来额外的好处,即初始化代码不必从堆栈到内存的2MB副本(它只需要复制一些指针) .

    直接翻译为使用 Box

    pub static ref A_INTERSECTS_A: Box<[[bool; A_COUNT as usize]; A_COUNT as usize]> = {
        let mut result = Box::new([[false; A_COUNT as usize]; A_COUNT as usize]);
        // ...
    }
    

    遗憾的是: Box::new 是一个正常的函数调用,因此它的参数直接放在堆栈上 .

    但是,如果您正在使用夜间编译器并且愿意使用不稳定的功能,则可以使用"placement box",它实际上是为此目的而设计的:它在堆上分配空间并将值直接构造到该内存中,从而避免使用中间副本,并且避免了将数据放在堆栈上的需要 . 这只需要用 box 替换 Box::new

    let mut result = box [[false; A_COUNT as usize]; A_COUNT as usize];
    

    如果你(非常明智地)更喜欢坚持稳定版本,那么直到稳定的替代方法是用 Vec 替换数组的外层:这保留了数组的所有数据局部优势(一切都在内存中连续布局) ),虽然在静态知识方面稍微弱一点(编译器不能确定长度是1356) . 由于 [_; A_COUNT] 没有实现 Clone, this cannot use the vec!`宏,因此(不幸的是)看起来像:

    pub static ref A_INTERSECTS_A: Vec<[bool; A_COUNT as usize]> = {
        let mut result =
            (0..A_COUNT as usize)
                .map(|_| [false; A_COUNT as usize])
                .collect::<Vec<_>>();
        // ...
    }
    

    如果你绝对需要所有数组,可以做一些 unsafe 魔法将其从 Vec 中提取到原始的 Box<[[bool; ...]; ...]> . 它需要两个步骤(通过 into_boxed_slice ),因为 Box<T> 需要为 T 完美地分配大小,而 Vec 可能需要进行分配以实现其O(1)摊销 . 这个版本看起来像:

    pub static ref A_INTERSECTS_A: Box<[[bool; A_COUNT as usize]; A_COUNT as usize]> = {
        let mut result =
            (0..A_COUNT as usize)
                .map(|_| [false; A_COUNT as usize])
                .collect::<Vec<_>>();
    
        // ...
    
        // ensure the allocation is correctly sized
        let mut slice: Box<[[bool; A_COUNT as usize]]> = result.into_boxed_slice();
        // pointer to the start of the slices in memory
        let ptr: *mut [bool; A_COUNT as usize] = slice.as_mut_ptr();
        // stop `slice`'s destructor deallocating the memory
        mem::forget(slice);
    
        // `ptr` is actually a pointer to exactly A_COUNT of the arrays! 
        let new_ptr = ptr as *mut [[bool; A_COUNT as usize]; A_COUNT as usize];
        unsafe {
            // let this `Box` manage that memory
            Box::from_raw(new_ptr)
        }
    }
    

    我进入的时间要清楚一些 . 这是因为 Vec<T> 暴露 into_boxed_slice ,因此我们可以将 Box<[T]> (即动态长度)变成 Box<[T; len]> ,因为我们知道编译时的确切长度 .

相关问题