创建大型数组时,线程“<main>”已溢出其堆栈

Posted

技术标签:

【中文标题】创建大型数组时,线程“<main>”已溢出其堆栈【英文标题】:thread '<main>' has overflowed its stack when creating a large array 【发布时间】:2016-05-07 16:13:47 【问题描述】:

以下代码中的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&lt;Vec&lt;Vec&lt;bool&gt;&gt;&gt; 代码可以正常工作,但我真的很想在这里使用数组。

【问题讨论】:

【参考方案1】:

这里的问题几乎可以肯定是当A_INTERSECTS_A 的初始化代码运行时被放置在堆栈上的巨大result 数组。它是 13562 &approx; 1.8 MB,与堆栈大小的数量级相似。事实上,它大于 Windows 的默认大小 1 MB(我怀疑您使用的是 Windows,因为您收到了该错误消息)。

这里的解决方案是通过将堆栈移动到堆来减小堆栈大小,例如,使用Vec 代替(如您所指的那样),或使用Box。这将带来额外的好处,即初始化代码不必从堆栈复制 2MB 到A_INTERSECTS_A 的内存(它只需要复制一些指针)。

直接翻译为使用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::new 替换为box

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

如果您(非常明智地)更喜欢坚持使用稳定版本,那么在稳定之前的替代方法是用Vec 替换数组的 外层:这会保留所有数据局部性数组的好处(所有内容都在内存中连续布局),尽管在静态知识方面稍弱(编译器不能确定长度是 1356)。由于 [_; A_COUNT] 没有实现 Clone, this cannot use thevec!` 宏,因此(不幸的是)看起来像:

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&lt;[[bool; ...]; ...]&gt;。它需要两个步骤(通过into_boxed_slice),因为Box&lt;T&gt; 需要为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&lt;T&gt; 暴露了into_boxed_slice,因此我们可以将Box&lt;[T]&gt;(即动态长度)转换为Box&lt;[T; len]&gt;,因为我们知道编译时的确切长度。

【讨论】:

是的,我尝试使用Box::new,但没有成功。不知道box。谢谢,现在说得通了。

以上是关于创建大型数组时,线程“<main>”已溢出其堆栈的主要内容,如果未能解决你的问题,请参考以下文章

如何提供对大型数组元素的线程安全访问

线程 '<main>' 在 Rust 中溢出了它的堆栈

创建数组的动态数组[关闭]

多线程比单线程慢

Windows 中的多线程 - 创建函数指针数组时出错

扩展线程运行时的c ++并发问题