首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >记忆混乱

记忆混乱
EN

Stack Overflow用户
提问于 2013-05-09 23:53:22
回答 4查看 137关注 0票数 5

我有一个应用程序,我正在尝试创建一个非常大的“立方体”的字节。一个三维数组(~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.5 me的内存被占用时,我最初收到了错误消息,但我认为将其切换到64位应用程序会让我在失败之前使用更多的内存。

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

提前感谢--我期待着任何人都能对此行为有所了解。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-05-10 00:38:05

编辑

我在思考我的答案的一个更完整的功能实现,我想我应该附加。我不确定并行化是否会有帮助,也许这取决于initializer

代码语言:javascript
复制
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;
}            

这使得函数完全是泛型的,但仍然保留了简单的语法,

代码语言:javascript
复制
var foo1 = NewJagged<Foo>(1000, 1000, 500);

但是,您可以在初始化时使用并行方式进行填充,

代码语言:javascript
复制
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;
        });

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

最初,这似乎不能解决您的问题……

如果你有一个函数,就像这样

代码语言:javascript
复制
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;
}

然后,您将封装引用类型的三维交错数组的初始化。这将允许你做,

代码语言:javascript
复制
Foo[][][] foo1 = ThreeDimmer<Foo>(1000, 1000, 500);

这将避免多维数组的内存碎片问题。它还可以避免other pitfalls and limitations,为您提供更快、更灵活的锯齿数组。

票数 3
EN

Stack Overflow用户

发布于 2013-05-09 23:59:24

问题是,当您分配一个数组时,您需要连续的内存。

在RAM中找到10个连续的内存块,每个内存块的大小为10MB,而不是找到一个大小为100MB的巨大内存块。

假设你有100字节的RAM,地址是0到99。例如,如果您在位置23分配了一个大小为1字节的内存块,尽管您还有99字节的内存,但是如果您想分配一个大小为99字节的内存块,那么您将失败,因为内存必须是连续的。在这种情况下,您能够分配的最大块将是76字节长。

票数 4
EN

Stack Overflow用户

发布于 2013-05-10 00:40:09

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更改为8 TB。但是,即使在这种情况下,您也应该记住,任何.NET对象的默认最大大小都是2 GB。详情请参见this question

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16465935

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档