【问题标题】:C# array of objects, very large, looking for a better wayC#对象数组,非常大,正在寻找更好的方法
【发布时间】:2011-06-27 10:39:41
【问题描述】:

好的,所以在我的一个项目中,我试图改变它存储某些变量的方式,我有一个简单的对象数组。这些对象引用的类是:

class Blocks
{
    public byte type = Block.Empty;
    byte lastblock = Block.Zero;
}

我打算添加更多,在当前类中type是对象当前值是什么,lastblock是对象曾经是什么。

我这样创建数组:

blocks = new Blocks[width * depth * length];

for (int i = 0; i < ((width * length) * depth); i++)
{
    blocks[i] = new Blocks();
}

我遇到的问题是,当我创建一个非常大的数组(对于那些不喜欢数学的人来说,512,512,512 或 134217728)时,数组会变得很大,超过 3.5 gig。

创建这个数组的旧方法有点简单,但更难扩展,它简单地创建了一个表示当前块的字节数组,并且在实际加载时似乎只使用 2 兆内存(我不要得到,因为 134217728 字节应该在 134 兆左右......对吗?)。对象引用会产生更多的内存使用,这让我感到困惑。

我做错了什么,还是应该回到原来的做法?我想要对象引用只是因为这意味着我的所有变量都在 1 个数组中,而不是在 4 个单独的数组中,这似乎似乎对系统更好。

编辑:

在尝试了几种不同的方法后,我发现改变了

class Blocks

struct Blocks

创造了一个不同的世界,感谢社区为将来使用提供欢迎提示,不幸的是我不想只在结构中添加两个字节并称之为完成,那是我停下来测试我的设计并缠绕的地方解决原来的问题。在向结构添加任何其他内容之后(至少在我的列表中的任何其他内容,意味着玩家对象引用或玩家名称字符串。)它会导致内存不足异常,这意味着我将无法毕竟使用这个系统。

但是知道如何做这件事在未来会很有帮助。为此,再次感谢您。

【问题讨论】:

  • 如何获得内存使用次数?他们似乎是这样的。
  • Blocks 是引用类型有什么原因吗?对我来说,它似乎是一个很好的候选结构(不可变或不可变)。
  • 对象引用会产生更多的内存使用,这让我感到困惑。 - 使用引用类型时会有相关的开销。这并不多,但是当您谈论数亿个对象时,它就会加起来。结构是其成员的大小。
  • 我使用了 taskmanager(是的,我知道它不是有史以来最好的东西),当我运行原始代码时,程序使用量从 20 兆跃升至 ~3.6 兆(每个对象都有一些移动变量) -生病看看结构:D
  • 关于您的更新:为什么每个块都需要引用玩家名称,或者更糟糕的是,嵌入在其数据结构中的玩家对象?这真是大材小用!

标签: c# .net


【解决方案1】:

您可以使用List 类来管理无限数量的对象。请查看我提供的链接。您可以将无限(理论上)数量的对象添加到列表中。

使用列表,您可以通过索引轻松访问任何项目。它还具有搜索、排序和操作其中包含的对象的方法。

如果您使用列表,您的代码将如下所示 -

List<Blocks> blocks = new List<Blocks>();

for (int i = 0; i < ((width * length) * depth); i++)  // The amount of items that you want to add
{
    Blocks b = new Blocks();
    blocks.Add(b);
}

您可以按如下方式访问此列表中的每个项目 -

foreach(Blocks b in blocks)
{
    // You have the object, do whatever you want
}

您可以找到列表中包含的对象的任何特定索引。看到这个method example

因此使用列表,您将能够以统一的方式轻松管理大量对象。

要了解更多信息,请转至here

【讨论】:

  • 我经常使用列表,但组的大小始终是固定的,具体取决于相关地图的大小,从我所读到的内容来看,如果你不这样做,最好使用数组't需要来自列表的功能。
【解决方案2】:

这是我尝试使用结构类型而不是类类型

public struct Block
{
    public byte _current;
    public byte _last;
}

public static void RunSnippet()
{
    Block[] blocks = new Block[512 * 512 * 512];

    for (int i = 0; i < ((512 * 512) * 512); i++)
    {
        blocks[i] = new Block();
    }
}

sn-p 几乎立即运行并占用大约 267 Mb 的 RAM。

如果可能,请尝试 struct

【讨论】:

  • 没有必要调用'new Block()',因为它们是值类型并且已经被归零为它们的默认值。也许这可以解释为什么您的内存使用量正好是 OP 理想预期的两倍?因为本质上你要创建每个块两次......
  • @MattDavey 在 for 循环中,用初始化结构字段(具有 0 值)的语句替换新的 Block() 语句仍会产生 267MB 的 RAM。内存的实际分配是在 for 循环中完成的,它不会做任何事情两次,而是只做它被告知的事情,即创建这么多结构并初始化为 0。
  • 你说得对,512*512*512*2 约为 267MB,OP 的原始估计有误。不过我想澄清一件事——实际的内存分配不是在 for 循环中完成的,而是在实例化数组时完成的。
  • @MattDavey 实际上注释 for 循环会导致大约 6Mb 的内存分配,这意味着编译器只分配足够的内存来保存指向 Block 类型数组的指针。 (所以内存消耗在 for 循环中)
  • 当我进行原始计数时,我忘记将它加倍 :P(2 个字节),但是是的,该结构在内存使用方面的效果更好,并且应该提供我需要的功能。我什至忘了尝试结构(之前只读过它们,不需要自己创建一个)
【解决方案3】:

阅读:Object Overhead: The Hidden .NET Memory Allocation Cost

您的对象的总成本约为 16 个字节(在 32 位系统上)。 (“标题”8 个字节,字段 4 个字节,引用 4 个字节)和 512 * 512 * 512 * 16 = 2.1gb :-)

但你可能在 64 位系统上,所以它是 16 + 8 + 8 = 28,所以 4.29gb

【讨论】:

  • 这解释了大小 O_O 的巨大增长
【解决方案4】:

您应该考虑使用“struct”而不是“class”。

http://msdn.microsoft.com/en-us/library/ah19swz4(v=vs.71).aspx

"struct类型适用于表示Point、Rectangle、Color等轻量级对象。虽然可以将点表示为类,但struct在某些场景下效率更高。例如,如果声明一个包含 1000 个 Point 对象的数组,您将为引用每个对象分配额外的内存。在这种情况下,结构更便宜。”

如果您尝试这样做,请发布您的结果。

【讨论】:

    【解决方案5】:

    当您创建数组时,您还会在每个单元格上实例化一个 Blocks。你真的需要这样做吗?

    blocks[i] = new Blocks();

    当你不实例化一个块时,你只会有一个空引用数组。在访问代码中,您可以检查 null 并返回默认值。大致如下:

    if(blocks[i,j] == null)  return new  Blocks();
    else return blocks[i,j];
    

    写的时候还要检查是否有,如果没有,先创建。那应该可以节省很多内存。

    使用交错数组或嵌套列表也应该有很大帮助。

    问候 GJ

    【讨论】:

    • 如果我完成了块的实例化,当我加载填充块的文件时遇到错误,在任何时候块都必须有一个值(即使值是 0,小于一半的时间)
    • 我继续并稍微更改了代码,看看它是否可以在没有该行的情况下工作,它确实可以,但结果相同,我可能可以重新编写它,如果它没有'没有值,它返回 0 表示空,但这可能意味着要重新设计整个系统,我会留下那条线,所以我可以使用它,谢谢。
    • 这完全取决于矩阵的稀疏程度。不到一半的时间仍然可以节省大量内存,但是您必须更改访问数组的代码,或者更好地将数组封装在一个类中。根据块可以有多少唯一值,您可能还想查看享元模式。如果有很多重复的块具有相同的值,则不必创建单独的实例,数组中的多个单元格可以指向同一个实例。这需要一些管道代码来保持井井有条。
    【解决方案6】:

    结构是前进的方向,并且会打开使用不安全代码/指针算法进行优化的可能性

    struct Block
    {
        byte Current;
        byte Last;
    }
    
    Block[] blocks = new Block[512 * 512 * 512];
    
    unsafe
    {
        Block* currentBlock = &blocks;
    
        for (int i = 0; i < (512 * 512) * 512; i++)
        {
            currentBlock->Current = 0xff;
            currentBlock->Last = 0x00;
    
            currentBlock++;
        }
    }
    

    当然有人会说可变结构是邪恶的! (仅当您不知道如何使用它们时)

    【讨论】:

    • 我从来没有使用过“不安全”的代码,也不知道它在哪里以及为什么适用,但从名字上看,这似乎不是一个好主意。但是很多人建议了结构,我会研究一下。
    • 这是个坏建议。不仅仅是可变结构的原因。
    • 那为什么呢?请详细说明。我并不是说他必须这样做,但速度对他来说似乎是个问题,这将是最快的方法。
    • 速度不是真正的问题,除非运行服务器的人的 CPU 速度较慢,否则我想更改程序的结构以提高速度和内存使用率是的,但这并不意味着我想为此发疯。
    • 公平地说,不安全代码和不可变结构的优缺点远远超出了这个问题的范围:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-16
    • 2021-05-03
    • 1970-01-01
    相关资源
    最近更新 更多