【问题标题】:How come my class take so much space in memory?为什么我的课会占用这么多内存空间?
【发布时间】:2012-02-12 10:35:48
【问题描述】:

我将拥有数千万个 MyClass 类的实例,并希望最小化它的内存大小。在Find out the size of a .net object 中讨论了测量对象在内存中占用多少空间的问题。 我决定听从 Jon Skeet 的建议,这是我的代码:

   // Edit: This line is "dangerous and foolish" :-) 
   // (However, commenting it does not change the result)
   // [StructLayout(LayoutKind.Sequential, Pack = 1)]
   public class MyClass       
   {
      public bool isit;
      public MyClass nextRight;
      public MyClass nextDown;
   }

   class Program
   {
      static void Main(string[] args)
      {
         var a1 = new MyClass(); //to prevent JIT code mangling the result (Skeet)
         var before = GC.GetTotalMemory(true);   
         MyClass[] arr = new MyClass[10000];
         for (int i = 0; i < 10000; i++)
            arr[i] = new MyClass(); 

         var after = GC.GetTotalMemory(true);

         var per = (after - before) / 10000.0;
         Console.WriteLine("Before: {0} After: {1} Per: {2}", before, after, per);
         Console.ReadLine();
      }
   }

我在 64 位 Windows 上运行该程序,选择“发布”,平台目标:“任何 cpu”,然后选择“优化代码”(这些选项仅在我明确针对 x86 时才重要)结果遗憾的是 48 字节每个实例。

我的计算将是每个引用 8 个字节,加上 1 个用于 bool 的字节加上大约 8 个字节的开销。到底是怎么回事?这是一个让 RAM 价格居高不下和/或让非 Microsoft 代码膨胀的阴谋吗?好吧,好吧,我想我真正的问题是:我做错了什么,或者我怎样才能最小化 MyClass 的大小?

编辑:我为我的问题草率道歉,我编辑了几个标识符名称。我具体而直接的关注​​是构建一个“2-dim 链表”作为稀疏布尔矩阵实现,我可以在其中轻松获取给定行/列中设置值的枚举。 [当然这意味着我还必须在类中存储 x,y 坐标,这让我的想法更加不可行]

【问题讨论】:

  • 等等,你是在一个字节的边界上打包并且你有一个一个字节的结构在一堆引用之前您知道这是多么危险和愚蠢吗? 许多架构上运行时的性能和正确性取决于指针是否正确对齐!特别是易失性和原子性语义完全取决于对齐是否正确。如果运行时实际上是按照您的说明进行操作并将这些指针错位一个字节,那么您将遇到大问题。
  • 我认为我们必须查看您的 MyClass 代码才能帮助解决该问题。
  • Lippert 先生,不,我不知道,请纠正我,我不在乎布局,我只是想缩小尺寸。
  • @AllenG :这几乎是实现,除了会有属性而不是字段。我正在尝试使用“二维链表”实现稀疏布尔矩阵的想法。
  • 只有将它们放在数组中并且它们是值类型时才会节省空间。他们仍然会遇到@EricLippert 指出的问题,甚至可能更多。当然,将它们自己的类型作为字段引用是行不通的。

标签: c# memory-management


【解决方案1】:

从另一端解决问题。而不是问自己“我怎样才能使这个数据结构更小,并且仍然分配数千万个?”问问自己“我怎样才能使用完全不同的、更紧凑的数据结构来表示这些数据?”

看起来您正在构建一个布尔值的双向链表,正如您所注意到的,它使用的内存是所需内存的 30 到 50 倍。您是否有某些原因不简单地使用 BitArray 来存储您的布尔列表?

更新:

其实我是在尝试实现一个稀疏的布尔二维矩阵

那你为什么一开始不这么说呢?

当我想创建一个巨大的稀疏布尔二维矩阵时,我使用记忆工厂构建了一个不可变的持久布尔四叉树。如果数组是稀疏的,或者即使它是密集的但在某些方面自相似,您可以实现 巨大的 压缩。由 264 x 264 布尔值组成的方形数组很容易表示,即使显然作为一个真实数组,这将比世界上存在的内存更多。

我一直在考虑写一系列关于这种技术的博客文章;我可能会在三月下旬这样做。 (更新:那篇文章不是我在 2012 年 3 月写的;我是在 2020 年 8 月写的。https://ericlippert.com/2020/08/17/life-part-32/

简单地说,这个想法是创建一个抽象类 Quad,它有两个子类:Single 和 Multi。 “Single”是一个双例——就像一个单例,但只有两个实例,称为 True 和 False。 Multi 是具有四个子四边形的 Quad,分别称为 NorthEast、SouthEast、SouthWest 和 NorthWest。

每个 Quad 都有一个整数“级别”; Single 的级别为零,而级别 n 的 multi 必须使其所有子节点都是级别 n-1 的 Quads。

Multi 工厂被记忆;当你要求它制作一个有四个孩子的新 Multi 时,它会查询缓存以查看它之前是否制作过。如果有,它不会构造一个新的;它分发旧的。由于 Quad 是不可变的,因此您不必担心在缓存中之后有人会更改您身上的 Quad。

现在考虑多少内存字(一个字是 4 或 8 个字节,具体取决于架构)一个“全为假”的 n 级 Multi 消耗了多少。一个 1 级“全为假”的 multi 使用四个词来表示其子级的链接,一个词用于级别计数(如果需要;您不需要将级别保留在 multi 中,尽管它有助于调试)和几个词对于同步块等等。姑且称之为八个字吧。 (加上 False Single quad 的内存,我们可以假设它是两个或三个字的常数,因此可以忽略。)

一个 2 级“全为假”的 multi 使用相同的八个单词,但它的四个孩子中的每一个都是相同的 1 级 multi。因此,2级“全错”多的总消耗量是16个字。

第 3 级、第 4 级、...等等也是如此。逻辑上为 264 x 264 布尔方数组的 64 级 multi 的总内存消耗仅为 64 x 16 内存字!

有意义吗?希望这足以让您继续前进。如果没有,请参阅上面的我的博客链接。

【讨论】:

  • 我编辑了我的问题,实际上我正在尝试实现一个稀疏布尔矩阵。我猜我可以使用 BitArray 的列表,但我的实现会更容易获取给定列中的所有设置值。 [当然,然后我还必须在课堂上保持 int x 和 y 坐标,现在我真的很难过]
  • 和“我怎么能得到几千万的效果,没有几千万”,按照查尔斯的回答。
  • @Ali:考虑为大型稀疏布尔矩阵使用不可变的持久布尔四叉树。我已经更新了我的答案以给出一个草图。
  • 你的博客会是关于 hashlife 的吗?
  • 仅仅过了 8 年,博客终于来了! :-)
【解决方案2】:

8(对象引用)+ 8(对象引用)+ 1(布尔值)+ 16(标题)+ 8(数组本身的引用)= 41

即使内部未对齐,每个都将在堆上对齐。所以我们正在寻找至少 48 字节。

不过,我一生都无法理解您为什么想要一个布尔链接列表。它们的列表将占用 48 倍的空间,而那是在您优化存储每个位的布尔值之前,这将使其缩小 384 倍。并且更容易操作。

【讨论】:

    【解决方案3】:

    如果这些数以亿计的类实例大多是类的副本,类属性值的变化很小,那么您的系统是使用所谓的Flyweight 模式。这种模式通过一遍又一遍地使用相同的实例来最小化内存使用,并且只需根据需要更改属性......

    【讨论】:

    • 如果他们将它们都放在同一个双向链表中,那么享元将无法工作,他们似乎这样做了。不过,享元通常是个好建议,所以 +1。
    猜你喜欢
    • 2013-12-03
    • 2021-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 2013-01-01
    相关资源
    最近更新 更多