记忆混乱

时间:2013-05-09 15:53:22

标签: c# vb.net memory memory-management

我有一个应用程序,我正在尝试创建一个非常大的“立方体”字节。一个3维数组(~1000x1000x500)保存了我感兴趣的所有值 - 但是我遇到了内存问题。虽然这是预期的,但我所获得的各种OOM消息的行为却令人困惑。第一:

Foo[,,] foo1 = new Foo[1000, 1000, 500];

因OOM错误而失败,但这不会:
Foo[,,] foo1 = new Foo[250, 1000, 500];
Foo[,,] foo2 = new Foo[250, 1000, 500];
Foo[,,] foo3 = new Foo[250, 1000, 500];
Foo[,,] foo4 = new Foo[250, 1000, 500];

这两组代码不应该消耗基本相同的内存量吗?

另外,当我消耗~1.5GB时,我最初收到错误消息,但我认为通过将其切换到64位应用程序会让我在失败前使用更多内存。

我是否遇到了堆栈空间限制?如果是这样,我怎样才能完全在堆上实例化这个结构,而不必在堆栈上存在(作为单个实体)?

提前致谢 - 我期待任何人都可以对这种行为进行抨击。

4 个答案:

答案 0 :(得分:4)

事实是,当你分配一个数组时,你需要连续的内存。

更有可能在RAM中找到10块连续内存,每块大小为10MB,而不是找到1块100MB的大块。

假设您有100个字节的RAM,地址为0到99。 例如,如果在位置23分配了大小为1字节的内存块,虽然剩下99字节的RAM,但如果要分配大小为99字节的内存块,则会因为内存必须是连续的。在这种情况下,你能够分配的最大块是76字节长。

答案 1 :(得分:4)

32位应用程序限制为4 GB的地址空间,因此这是上限。如果进程在32位操作系统上运行,则进一步限制为2或3 GB,具体取决于应用程序和操作系统的设置。

在第一种情况下,你要分配一个大数组。在.NET中,数组是在堆上分配的,因此堆栈空间不是问题所在。鉴于问题中的数字,我假设数组大约是1.5 GB。要处理CLR需要一个连续的内存块。如果在较小的块中分配相同数量的字节(如第二个示例中所示),运行时将更有可能根据需要分配内存。

尽管如此,至少有2 GB可用,你会认为1.5 GB应该不是问题,但事实是一个进程不会严格地从一端到另一端使用地址空间。一旦DLL加载到进程中,地址空间就会碎片化。

根据我的经验,32位托管应用程序(在32位操作系统上)通常限制在大约1.5 GB的堆空间,这可以解释您所看到的OOM。

在64位操作系统上运行相同的应用程序将使应用程序可以访问整个4 GB的地址空间,这将影响托管堆的可用空间。

将应用程序转换为64位应用程序,将地址空间大小从4 GB更改为8 TB。但是,即使在这种情况下,您也应该记住任何.NET对象的默认最大大小为2 GB。有关详细信息,请参阅this question

答案 2 :(得分:3)

对我而言,这看起来像是内存碎片问题。另请注意,new使用堆。

在第一个示例中,您要求一个非常大的内存块,无论系统中有多少RAM,操作系统都可能无法找到大的连续内存块。

较小的分配是有效的,因为较小的连续内存块总是比较大的内存块更丰富。

答案 3 :(得分:3)

修改

我正在思考一个更全面的实现我的答案,我想我会追加。我不确定并行化是否会有所帮助,可能取决于initializer

using System;
using System.Linq;

public static T[][][] NewJagged<T>(
        int h,
        int w,
        ing d,
        Func<int, int, int, T> initializer = null,
        bool parallelize = true)
{
    if (h < 1)
    {
        throw new ArgumentOutOfRangeException("h", h, "Dimension less than 1.")
    }

    if (w < 1)
    {
        throw new ArgumentOutOfRangeException("w", w, "Dimension less than 1.")
    }

    if (d < 1)
    {
        throw new ArgumentOutOfRangeException("d", d, "Dimension less than 1.")
    }

    if (initializer == null)
    {
        initializer = (i, j, k) => default(T);
    }

    if (parallelize)
    {
        return NewJaggedParalellImpl(h, w, d, initializer);
    } 

    return NewJaggedImpl(h, w, d, initializer);
}

private static T[][][] NewJaggedImpl<T>(
        int h,
        int w,
        int d,
        Func<int, int, int, T> initializer)
{
    var result = new T[h][][];
    for (var i = 0; i < h; i++)
    {
        result[i] = new T[w][];
        for (var j = 0; j < w; j++)
        {
            result[i][j] = new T[d];
            for (var k = 0; k < d; k++)
            {
                result[i][j][k] = initializer(i, j, k);
            }
        }
    }

    return result;
}

private static T[][][] NewJaggedParalellImpl<T>(
        int h,
        int w,
        int d,
        Func<int, int, int, T> initializer)
{
    var result = new T[h][][];
    ParallelEnumerable.Range(0, h).ForAll(i =>
    {
        result[i] = new T[w][];
        ParallelEnumerable.Range(0, w).ForAll(j =>
        {
            result[i][j] = new T[d];
            ParallelEnumerable.Range(0, d).ForAll(k =>
            {
                result[i][j][k] = initializer(i, j, k);
            });
        });
    });

    return result;
}            

这使得该函数完全通用,但仍然保留了简单的语法,

var foo1 = NewJagged<Foo>(1000, 1000, 500);

然而,你可以在初始化时获得幻想和填充,

var foo2 = NewJagged<Foo>(
    1000,
    1000,
    5000,
    (i, j, k) =>
        {
            var pos = (i * 1000 * 500) + (j * 500) + k;
            return ((pos % 2) == 0) ? new Foo() : null;
        });

在这种情况下,填充棋盘效果(我认为。);


这可能最初似乎没有回答你的问题...

如果你有一个功能,就像这样

public static T[][][] ThreeDimmer<T>(int h, int w, int d) where T : new()
{
    var result = new T[h][][];
    for (var i = 0; i < h; i++)
    {
        result[i] = new T[w][];
        for (var j = 0; j < w; j++)
        {
            result[i][j] = new T[d];
            for (var k = 0; k < d; k++)
            {
                result[i][j][k] = new T();
            }
        }
    }

    return result;
}

然后你将封装初始化参考类型的3维锯齿状数组。这将允许你这样做,

Foo[][][] foo1 = ThreeDimmer<Foo>(1000, 1000, 500);

这可以避免多维数组的内存碎片问题。它也会避免使用other pitfalls and limitations,而是为您提供更灵活的锯齿状阵列。