为什么我班上的内存占用了这么多空间?

时间:2012-01-17 16:01:14

标签: c# memory-management

我将拥有一些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加上一些~8byte的开销。到底是怎么回事?这是一个保持RAM价格高和/或让非Microsoft代码膨胀的阴谋吗?好吧,好吧,我想我真正的问题是:我做错了什么,或者我怎样才能最小化MyClass的大小?

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

3 个答案:

答案 0 :(得分:26)

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

看起来你正在构建一个双向链接的bool列表,正如你所说,它使用的内存比它需要的内存多30到50倍。您是不是只使用BitArray来存储您的bool列表?

更新:

  实际上我试图实现一个稀疏的布尔二维矩阵

那你为什么不首先这么说呢?

当我想制作一个庞大的稀疏布尔二维矩阵时,我用一个memoized工厂构建了一个不可变的持久布尔四叉树。如果数组是稀疏的,或者即使它是密集但以某种方式自相似,你也可以实现巨大的压缩。 2 64 x 2 64 布尔运算的方阵可以很容易地表示,即使显然是一个真实的数组,这将是比世界上存在的更多的记忆。

我一直在想着做一系列关于这种技术的博客文章;我可能会在三月下旬这样做。

简而言之,我们的想法是创建一个抽象类Quad,它有两个子类:Single和Multi。 “单一”是一个双重身份 - 就像一个单身人士,但只有两个实例,称为True和False。 Multi是具有四个子四边形的四边形,称为NorthEast,SouthEast,SouthWest和NorthWest。

每个Quad都有一个整数“级别”; Single的级别为零,并且需要多级n级才能使其所有子级为级别为n-1的四元组。

多工厂被记忆;当你要求它制作一个有四个孩子的新Multi时,它会查询缓存以查看它是否已经成功。如果有,它不会构建一个新的;它分发了旧的。由于Quads是不可变的,所以你不必担心有人在你进入缓存后更改Quad。

现在考虑一下有多少个记忆单词(一个单词是4或8个字节,具体取决于体系结构)和“全部错误”等级n的多个消耗。级别1“全部为假”多个消耗四个单词用于指向其子级的链接,一个用于级别计数的单词(如果需要,您不需要保持多级中的级别,尽管它有助于调试)和一些单词用于同步块等。我们称之为八个字。 (加上False Single quad的内存,我们可以假设这是两个或三个单词的常量,因此可以忽略。)

2级“全假”多消耗相同的八个单词,但其四个孩子中的每一个都是相同的1级多。因此,2级“全假”的总消耗量可以说是16个字。

3级,4级......等等。 64级多路复用的总内存消耗在逻辑上是2 64 x 2 64 方形布尔数组,只有64 x 16个内存字!

有意义吗?希望这足以让你前进。如果没有,请在3月下旬看我的博客。

答案 1 :(得分:4)

8(对象引用)+ 8(对象引用)+ 1(bool)+ 16(标题)+ 8(数组本身引用)= 41

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

我不能为我的生活看到为什么你想要一个链接的bool列表。它们的列表将占用48倍的空间,并且在您获得每位存储bool的优化之前,这将使其小384倍。而且更容易操纵。

答案 2 :(得分:1)

如果该类的数亿个实例大多是类的副本,并且类属性值的变化很小,那么您的系统是使用所谓的 {{3}的主要候选者。 } 模式。这种模式通过反复使用相同的instanes来最小化内存使用,并且只需根据需要更改属性......